From 367db7d05ecf9af4b830621b219387c26a3aad48 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 17:01:31 +0000 Subject: [PATCH 1/3] fix(app-shell): a Studio selection unfolds a hidden right panel at every width With the right panel hidden, selecting a flow node (or an Interfaces block, or a Data field) set the slot to its properties but reopened it only on a phone, so at a desktop width the selection's configuration was drawn nowhere. The slot's showProps now unfolds it at every width, as the slot toggle and the slot shortcut do. It is held in state for the surface's life, so a pillar asks again only on a new selection, never because the callback was re-created. Claude-Session: https://claude.ai/code/session_01QsR2jRkRJ1Po9y5UgWCsns Co-authored-by: Claude --- .../26737-studio-selection-unfolds-panel.md | 17 +++++++++ ...ignSurface.pillarsClaimSlot-12725.test.tsx | 37 +++++++++++++++---- ...udioDesignSurface.rightSlot-12725.test.tsx | 13 +++++++ .../studio-design/StudioDesignSurface.tsx | 15 ++++++-- 4 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 .changeset/26737-studio-selection-unfolds-panel.md 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 76808115b16..15f41dcfe25 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 From f5a1b6ce2905336f3993bdb97d024c0d597baed9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 18:06:28 +0000 Subject: [PATCH 2/3] test(app-shell): load the studio zh catalogue before the flow-node inspector string cases The engine.inspector.flowNode rows live in the lazily fetched studio zh catalogue; under this branch's Test UI shard order the file ran before anything loaded it and read the English fallback. Ported byte-identically from the same fix on the branch claude/project-thread-998cn0 so the two merge cleanly. Claude-Session: https://claude.ai/code/session_01QsR2jRkRJ1Po9y5UgWCsns Co-authored-by: Claude --- .../inspectors/inspectorStrings.i18n-10804.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.i18n-10804.test.tsx b/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.i18n-10804.test.tsx index 2882f59f6ae..d0fc657be8c 100644 --- a/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.i18n-10804.test.tsx +++ b/ui/packages/app-shell/src/views/metadata-admin/inspectors/inspectorStrings.i18n-10804.test.tsx @@ -47,6 +47,12 @@ import * as React from 'react'; import { describe, it, expect, vi, afterEach } from 'vitest'; import { render, screen, cleanup, fireEvent, act, within } from '@testing-library/react'; import { I18nProvider } from '@object-ui/i18n'; +// The `engine.inspector.flowNode.*` rows live in `@object-ui/i18n`'s `studio` +// namespace (#25922 slice S2), whose `zh` catalogue is fetched on demand. +// Resident before any case, so `zhRow` reads the zh row, never the en fallback +// while the chunk is in flight (objectstack#26692). +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); // Every roster in these inspectors comes through the shared metadata client // (stable identity, like the real memoized client). Nothing here needs a From 084cf97ee2b74007f871ed6293a0803476b976a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 18:15:35 +0000 Subject: [PATCH 3/3] test(app-shell): load the studio zh catalogue before the flow canvas chip cases Same shard-order cause as the inspector string test: the zh "API name" title lives in the lazily fetched studio catalogue. Ported byte-identically from the same fix on the branch claude/project-thread-998cn0 so the two merge cleanly. Claude-Session: https://claude.ai/code/session_01QsR2jRkRJ1Po9y5UgWCsns Co-authored-by: Claude --- .../StudioDesignSurface.flowCanvasChip-11665.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.flowCanvasChip-11665.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.flowCanvasChip-11665.test.tsx index c3b89f8757f..2a0420a9320 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.flowCanvasChip-11665.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.flowCanvasChip-11665.test.tsx @@ -24,6 +24,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, cleanup } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { I18nProvider } from '@object-ui/i18n'; +// The chip's tooltip row (`designer.field.apiName`) lives in `@object-ui/i18n`'s +// `studio` namespace, whose `zh` catalogue is fetched on demand. Resident before +// any case, so the zh tooltip is read after the catalogue lands, never racing it +// (objectstack#26692). +import { loadStudioCatalogue } from '@object-ui/i18n/namespaces/studio'; +await loadStudioCatalogue('zh'); const PKG = 'com.acme.app'; const API_NAME = 'nightly_digest';