Skip to content

Manager: Fix layout.showPanel config#34777

Merged
Sidnioulz merged 8 commits into
storybookjs:nextfrom
kalinco-glitch:codex/fix-layout-show-panel
May 29, 2026
Merged

Manager: Fix layout.showPanel config#34777
Sidnioulz merged 8 commits into
storybookjs:nextfrom
kalinco-glitch:codex/fix-layout-show-panel

Conversation

@kalinco-glitch
Copy link
Copy Markdown
Contributor

@kalinco-glitch kalinco-glitch commented May 12, 2026

Closes #32062

What I did

Fixed layout.showPanel so manager config can set the default addon panel visibility.

  • Added layout.showPanel and layout.showSidebar typing for manager config layout options
  • Applied layout.showPanel: false by collapsing the stored addon panel sizes during initial config and runtime setOptions
  • Preserved recent visible panel sizes so users can reopen the panel after it starts hidden
  • Kept layoutCustomisations.showPanel as the stronger per-context override path
  • Added layout API tests for initial config and runtime setOptions

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

EDIT by @Sidnioulz:

Between each of these tests, clear local storage and restart Storybook.

First, set this in code/.storybook/manager.tsx:

addons.setConfig({
  layout: {
    showNav: false,
    showPanel: false,
    showToolbar: false,
  },
  sidebar: {
    renderLabel: ({ name, type }) => (type === 'story' ? name : startCase(name)),
  },
  ui: {
    enableShortcuts: false,
  },
});

You should see no sidebar, toolbar or panel, and no keyboard shortcut should work (make sure to test keyboard shortcuts while focused in the SB ui and not in the preview).

Next, set this in code/.storybook/manager.tsx:

addons.setConfig({
  showNav: false,
  showPanel: false,
  showToolbar: false,
  enableShortcuts: false,
  sidebar: {
    renderLabel: ({ name, type }) => (type === 'story' ? name : startCase(name)),
  },
  ui: {
    enableShortcuts: false,
  },
});

You should see the same thing but with deprecation warnings in the console.

Next, set this in code/.storybook/manager.tsx:

addons.setConfig({
  showNav: false,
  showPanel: false,
  showToolbar: false,
  enableShortcuts: false,
  layout: {
    showNav: true,
    showPanel: true,
    showToolbar: true,
  },
  sidebar: {
    renderLabel: ({ name, type }) => (type === 'story' ? name : startCase(name)),
  },
  ui: {
    enableShortcuts: true,
  },
});

You should see the whole UI and have working keyboard shortcuts, alongside deprecation warnings, because the well-placed config keys take precedence over top-level keys.


Validated locally with:

  1. git diff --check
  2. oxfmt --check code/core/src/types/modules/api.ts code/core/src/types/modules/addons.ts code/core/src/manager-api/modules/layout.ts code/core/src/manager-api/tests/layout.test.ts
  3. A focused TypeScript check on the changed files

I was not able to run the targeted local Vitest suite because this sparse checkout could not resolve built internal Storybook workspace packages such as @storybook/addon-vitest. I also could not run the targeted ESLint command because this checkout could not resolve the internal eslint-plugin-storybook package from code.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update MIGRATION.MD

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Layout elements (sidebar, panel, toolbar) can be toggled via configuration; disabling hides dimensions (set to zero) while preserving previous sizes for restoration.
    • Add-on configuration now accepts optional layout overrides so extensions can provide layout options.
    • Layout option merging and show/hide behavior are applied consistently on initial load and at runtime.
  • Tests

    • Added tests for layout visibility controls and initial layout behavior when visibility flags are applied.

Review Change Stack

@kalinco-glitch kalinco-glitch marked this pull request as ready for review May 13, 2026 14:13
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds required layout flags and addon typing; implements applyLayoutOptions to merge layout options, deprecate legacy top-level keys, and map showNav/showPanel to size fields (zeroing/restoring); wires helper into getInitialOptions and api.setOptions; expands tests for visibility, preservation, and deprecation precedence.

Changes

Layout Options Type and Application

Layer / File(s) Summary
API and addon type updates
code/core/src/types/modules/api.ts, code/core/src/types/modules/addons.ts
Adjusts API_Provider.getConfig() return type, removes API_UIOptions, adds required API_Layout.showNav/showPanel, and extends Addon_Config with layout?: Partial<API_Layout> and layoutCustomisations?: Partial<API_LayoutCustomisations>.
applyLayoutOptions implementation and defaults
code/core/src/manager-api/modules/layout.ts
Adds deprecate import, extends default layout state with showNav/showPanel and layoutCustomisations.showPanel, and implements applyLayoutOptions to safely merge recognized layout keys, emit deprecation warnings for legacy top-level keys, map showNav/showPanel into size fields using recentVisibleSizes, and enforce singleStory (navSize=0).
Layout initialization and option updates
code/core/src/manager-api/modules/layout.ts
Refactors getInitialOptions and api.setOptions to compute layout via applyLayoutOptions(..., options, singleStory) and compute ui via applyUiOptions, replacing prior spread-based merges and inline singleStory overrides.
Layout visibility and deprecation tests
code/core/src/manager-api/tests/layout.test.ts
Adds tests verifying setOptions/getInitialOptions with layout.showPanel/layout.showNav hide sizes (set to 0) while preserving recentVisibleSizes, and asserting nested layout/ui keys override deprecated top-level config and produce deprecation warnings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Add jsdoc explaining the capture-after-merge ordering of recentVisibleSizes,
plus inline notes on the unknown-key safety net and the singleStory nav guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
code/core/src/manager-api/modules/layout.ts (1)

196-204: 💤 Low value

Consider documenting the show*: true behavior for completeness.

The JSDoc clearly explains the show*: false case (capturing sizes after merge). For completeness, you might document that when show* = true, sizes are restored from recentVisibleSizes regardless of any explicit size values passed in the same call.

📝 Optional documentation enhancement
  * Numeric sizes from `options` are merged in first, so `recentVisibleSizes` is
  * captured *after* that merge — meaning if a caller passes both a size and
  * `show*: false` in the same payload, the new size is what we remember for
  * later restoration via `togglePanel(true)` / `toggleNav(true)`.
+ *
+ * When `show*: true`, sizes are always restored from `recentVisibleSizes`,
+ * ignoring any explicit size values passed in the same call.
  */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/manager-api/modules/layout.ts` around lines 196 - 204, Update
the JSDoc above the layout merge routine to explicitly document the "show*:
true" behavior: state that when `showSidebar` or `showPanel` is set to true the
implementation restores sizes from `recentVisibleSizes` (so those restored sizes
take precedence even if numeric size fields are provided in the same options
payload), and reference the related restore helpers like `togglePanel(true)` /
`toggleNav(true)` and the `recentVisibleSizes` field to make the behavior clear;
keep the note about numeric sizes being merged first and the `show*: false`
behavior as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@code/core/src/manager-api/modules/layout.ts`:
- Around line 196-204: Update the JSDoc above the layout merge routine to
explicitly document the "show*: true" behavior: state that when `showSidebar` or
`showPanel` is set to true the implementation restores sizes from
`recentVisibleSizes` (so those restored sizes take precedence even if numeric
size fields are provided in the same options payload), and reference the related
restore helpers like `togglePanel(true)` / `toggleNav(true)` and the
`recentVisibleSizes` field to make the behavior clear; keep the note about
numeric sizes being merged first and the `show*: false` behavior as-is.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cab6ab84-84ae-435a-8f65-03fb0f86eb8a

📥 Commits

Reviewing files that changed from the base of the PR and between 371ef9a and 39a9d4b.

📒 Files selected for processing (1)
  • code/core/src/manager-api/modules/layout.ts

- Fold the `singleStory` nav guard into the `showSidebar === false` branch
  so the function ends on a single return; equivalent behavior, easier to
  read.
- Add the missing `showPanel: undefined` entry to the default
  `layoutCustomisations` so it matches `API_LayoutCustomisations`.
- Omit `recentVisibleSizes` from `API_LayoutOptions` — it is internal
  restoration bookkeeping and not something callers should set via
  `addons.setConfig`.
- Note in `API_LayoutOptions` that `showToolbar` already comes from
  `Partial<API_Layout>` and is intentionally not redeclared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic
Copy link
Copy Markdown
Contributor

Live verification in internal Storybook UI

I applied this PR's changes onto a fresh checkout, recompiled core, and exercised the feature end-to-end through the actual manager UI (not just unit tests). All five angles behave as expected.

Test matrix

# Scenario Path exercised Observed result
1 addons.setConfig({ layout: { showPanel: false } }) initial config → getInitialOptionsapplyLayoutOptions Sidebar visible, canvas visible, no addon panel. Toolbar shows "Show addon panel". Addon-panel resize handle reports value="0".
2 Click the toolbar's "Show addon panel" after #1 runtime togglePanel(true) Panel restored to 300px — the default recentVisibleSizes.bottomPanelHeight. Controls / Actions / Interactions / Code / Design / Accessibility / Visual tests tabs all render. This proves recentVisibleSizes is preserved across the initial showPanel: false.
3 addons.setConfig({ layout: { showSidebar: false } }) initial config, sidebar branch of applyLayoutOptions No sidebar. Canvas + addon panel visible. Toolbar shows "Show sidebar". Sidebar resize handle reports value="0".
4 addons.setConfig({ layout: { showPanel: false, showSidebar: false } }) initial config, both branches Canvas only. Both restore buttons in toolbar. getIsFullscreen correctly reports true (panel + nav both 0) so the toolbar shows "Exit full screen".
5 __STORYBOOK_ADDONS_MANAGER.setConfig({ layout: { showPanel: false } }) from devtools, starting from default visible state runtime path — SET_CONFIG channel handler → api.setOptionsapplyLayoutOptions Panel hides without page reload. Sidebar untouched (300px). Toolbar updates to "Show addon panel".

What this confirms

  • The original bug (#32062) is fixed: layout.showPanel: false in manager config now actually hides the panel on first load.
  • The recentVisibleSizes preservation works in both directions — config-driven hide and user-driven restore.
  • showSidebar (also implemented by this PR but not in the linked issue) works symmetrically.
  • The SET_CONFIG runtime path is wired up correctly; users don't need to reload for the change to take effect.
  • The combined showPanel: false + showSidebar: false correctly triggers fullscreen-ish state via the existing getIsFullscreen predicate — no special-casing needed.

What I did not cover here

Looks good to me. 👍

The `Omit<Partial<API_Layout>, 'recentVisibleSizes'>` introduced in the
previous commit breaks the rollup-plugin-dts build of core because
`applyLayoutOptions` calls `pick(layoutOptions, layoutKeys)` where
`layoutKeys` is typed as `(keyof API_Layout)[]` (includes
`recentVisibleSizes`) but the destructured `layoutOptions` no longer
accepts it.

Reverting to `extends Partial<API_Layout>` restores the type compatibility.
The `recentVisibleSizes` field is internal restoration bookkeeping and
practically no caller sets it via `addons.setConfig`, so the
type-tightening was nice-to-have and not worth the breakage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic changed the title Fix layout.showPanel manager config Manager: Fix layout.showPanel config May 20, 2026
@Sidnioulz Sidnioulz self-requested a review May 20, 2026 08:50
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/core/src/manager-api/tests/layout.test.ts (1)

540-553: ⚡ Quick win

Mocking pattern does not follow coding guidelines.

The inline vi.spyOn().mockImplementation() pattern violates the guidelines which require:

  • Using vi.mock() with spy: true at the top of the file
  • Implementing mock behaviors in beforeEach blocks
  • Accessing mocks via vi.mocked()

As per coding guidelines: "Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests", "Implement mock behaviors in beforeEach blocks in Vitest tests", and "Avoid inline mock implementations within test cases in Vitest tests".

♻️ Suggested refactor to follow mocking guidelines
+vi.mock('storybook/internal/client-logger', { spy: true });
+
 describe('layout API', () => {
   let layoutApi: SubAPI;
   let store: Store;
   let provider: API_Provider<API>;
   let currentState: SubState & {
     selectedPanel: AddonsSubState['selectedPanel'];
     singleStory?: boolean;
   };

   beforeEach(() => {
+    vi.mocked(clientLogger.deprecate).mockReset();
     currentState = {
       ...getDefaultLayoutState(),

Then in tests:

     it('should prioritize options.layout over top-level layout keys', () => {
-      const deprecateSpy = vi.spyOn(clientLogger, 'deprecate').mockImplementation(() => {});
-
       layoutApi.setOptions({
         showNav: true,
         showPanel: true,
         layout: { showNav: false, showPanel: false },
       });

       expect(currentState.layout.navSize).toBe(0);
       expect(currentState.layout.bottomPanelHeight).toBe(0);
       expect(currentState.layout.rightPanelWidth).toBe(0);
-      expect(deprecateSpy).toHaveBeenCalled();
+      expect(vi.mocked(clientLogger.deprecate)).toHaveBeenCalled();
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/core/src/manager-api/tests/layout.test.ts` around lines 540 - 553, The
test uses an inline vi.spyOn(...).mockImplementation() which violates the
project's mocking guidelines; replace the inline spy with a top-level
vi.mock(...) that enables spying (spy: true) and move the mock implementation
into the file's beforeEach so tests remain declarative. Specifically, remove the
inline vi.spyOn(clientLogger, 'deprecate').mockImplementation(...) in the test,
configure vi.mock for the module that exports clientLogger with spy: true at the
top of the test file, and in beforeEach call
vi.mocked(clientLogger).deprecate.mockImplementation(() => {}) (or reset/restore
as appropriate) so the test can call layoutApi.setOptions(...) and assert
vi.mocked(clientLogger).deprecate was called without inline spies.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@code/core/src/manager-api/tests/layout.test.ts`:
- Around line 540-553: The test uses an inline
vi.spyOn(...).mockImplementation() which violates the project's mocking
guidelines; replace the inline spy with a top-level vi.mock(...) that enables
spying (spy: true) and move the mock implementation into the file's beforeEach
so tests remain declarative. Specifically, remove the inline
vi.spyOn(clientLogger, 'deprecate').mockImplementation(...) in the test,
configure vi.mock for the module that exports clientLogger with spy: true at the
top of the test file, and in beforeEach call
vi.mocked(clientLogger).deprecate.mockImplementation(() => {}) (or reset/restore
as appropriate) so the test can call layoutApi.setOptions(...) and assert
vi.mocked(clientLogger).deprecate was called without inline spies.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8ee79f6-b88d-4a5b-a18a-4ca5d2e4832d

📥 Commits

Reviewing files that changed from the base of the PR and between 503ed0c and 02b976b.

📒 Files selected for processing (4)
  • code/core/src/manager-api/modules/layout.ts
  • code/core/src/manager-api/tests/layout.test.ts
  • code/core/src/types/modules/addons.ts
  • code/core/src/types/modules/api.ts

Comment thread code/core/src/manager-api/modules/layout.ts
@Sidnioulz Sidnioulz merged commit 29bcfd9 into storybookjs:next May 29, 2026
137 of 139 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: layout.showPanel is in the docs, but doesn't work

3 participants