Skip to content

Conversation

@hpohlmeyer
Copy link
Contributor

@hpohlmeyer hpohlmeyer commented Aug 23, 2025

What I did

This PR contains two changes:

Export default constants with as const

The motivation for this, is that this ensures better type safety. A good example would be to pick a subset of the viewports.

import { MINIMAL_VIEWPORTS } from 'storybook/viewport';
import { pick } from 'remeda';

const Base = {
  parameters: {
    viewport: {
      options: pick(MINIMAL_VIEWPORTS, ['mobile1', 'mobile2', '']),
      //                                                      ^ 'tablet' | 'desktop'
    }
  }
} satisfies Story;

It also makes the InitialViewportKeys obsolete, because you can easily create it yourself. It would also allow to get the keys for the MINIMAL_VIEWPORTS, which currently is not possible. I’ve kept the InitialViewportKeys export though, to avoid introducing breaking changes.

// Without this PR
import type { InitialViewportKeys } from 'storybook/types';

// With this PR
import type { INITIAL_VIEWPORTS, MINIMAL_VIEWPORTS } from 'storybook/types';

type InitialViewportKeys = keyof typeof INITIAL_VIEWPORTS;
type MinimalViewportKeys = keyof typeof MINIMAL_VIEWPORTS;

Fix the listed viewports in the documentation

While looking at the exports, I’ve noticed that the documentation is not properly reflecting the export:

  • The desktop viewport is not listed in the list for the MINIMAL_VIEWPORTS
  • The INITIAL_VIEWPORTS list includes the entries from the MINIMAL_VIEWPORTS, but they are not part of the export
  • The ipad10p dimensions where not matching the dimensions from the export

I’ve adjusted the docs to be in-sync with the exports.

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

The changes are jut type-changes, there are no runtime changes.

  1. Import MINIMAL_VIEWPORTS and INITIAL_VIEWPORTS from storybook/viewports
  2. Check the type for each of them, it should be an exact readonly version of the exported objects

Documentation

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

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 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 canary-release-pr.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Added a new Desktop viewport preset (1024 × 1280) to the minimal set.
    • Viewport presets are now consistently exposed and available programmatically across the app.
  • Bug Fixes

    • Corrected iPad 10" viewport dimensions to 834 × 1112.
  • Documentation

    • Updated docs to add Desktop preset and remove deprecated mobile1, mobile2, and tablet entries.
    • Clarified and aligned documented dimensions and minimal/detailed preset lists.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Bot Settings | Greptile

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link
Contributor

@jonniebigodes jonniebigodes left a comment

Choose a reason for hiding this comment

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

@hpohlmeyer thanks for taking the time to address the changes in the documentation. We appreciate it 🙏 ! From my end all is good. But i'll defer to @ndelangen for the code changes.

Have a great day.

Stay safe

@nx-cloud
Copy link

nx-cloud bot commented Sep 2, 2025

View your CI Pipeline Execution ↗ for commit c0914ec

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 47s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-24 09:01:33 UTC

@ndelangen ndelangen changed the title Stricter types for viewport constant exports AddonViewport: Stricter types Sep 2, 2025
@storybook-app-bot
Copy link

storybook-app-bot bot commented Sep 2, 2025

Package Benchmarks

Commit: c0914ec, ran on 24 September 2025 at 09:14:28 UTC

No significant changes detected, all good. 👏

@ndelangen
Copy link
Member

@hpohlmeyer can you look at the CI failures? looks like the type strictness is causing some breakages, might be an easy fix?

@github-actions github-actions bot added the Stale label Sep 17, 2025
@valentinpalkovic
Copy link
Contributor

@jonniebigodes Can you please take a look!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Exports INITIAL_VIEWPORTS (replacing the previous INTERNAL INITIAL_VIEWPORTS_DATA) and updates InitialViewportKeys to derive from it. Applies stricter typing using as const satisfies ViewportMap for both INITIAL_VIEWPORTS and MINIMAL_VIEWPORTS. Internal call sites updated to use the exported viewport map (renames/casts in the vitest addon and Tool component, and stricter prop types). Documentation updated: adds a minimal desktop (1024×1280), removes mobile1, mobile2, and tablet from the detailed list, and corrects ipad10p to 834×1112.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Defaults as defaults.ts
  participant Vitest as vitest-plugin/viewports.ts
  participant Tool as Tool.tsx
  Note over Defaults: INITIAL_VIEWPORTS exported\nMINIMAL_VIEWPORTS exported\n(as const satisfies ViewportMap)
  Defaults->>Vitest: import { MINIMAL_VIEWPORTS, INITIAL_VIEWPORTS }
  Defaults->>Tool: import { MINIMAL_VIEWPORTS, INITIAL_VIEWPORTS }
  Vitest->>Vitest: options = { ...MINIMAL_VIEWPORTS, ...param.viewports, ...param.options }
  Vitest->>Vitest: if options[defaultViewport] then extract styles (width/height)
  Vitest-->>Defaults: read types/keys from INITIAL_VIEWPORTS
  Tool->>Tool: viewportName: keyof ViewportMap\nisRotated: boolean | undefined
  Tool->>Defaults: index options/INITIAL_VIEWPORTS as ViewportMap[viewportName]
  Note over Vitest,Tool: Both now consume the exported, readonly ViewportMap
Loading

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "AddonViewport: Stricter types" is concise and accurately summarizes the PR’s primary change — tightening TypeScript types for the viewport addon and exporting stricter viewport constants; it clearly signals the intent to reviewers. It is specific enough to convey the main change without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c17e0ce and c0914ec.

📒 Files selected for processing (1)
  • code/core/src/viewport/components/Tool.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
PR: storybookjs/storybook#32458
File: code/core/src/viewport/components/Tool.tsx:38-39
Timestamp: 2025-09-18T20:51:06.618Z
Learning: In viewport tool code, when using the `useGlobals` hook from storybook/manager-api, the third returned value `storyGlobals` is guaranteed by TypeScript to be defined (not undefined/null), making the `in` operator safe to use without additional null checks.

Applied to files:

  • code/core/src/viewport/components/Tool.tsx
🔇 Additional comments (4)
code/core/src/viewport/components/Tool.tsx (4)

28-28: viewportName is too strict; allow 'responsive' and undefined

At runtime this can be 'responsive' or undefined. Tightening to keyof ViewportMap is misleading and causes typing friction downstream.

Apply this diff:

-  viewportName: keyof ViewportMap;
+  viewportName: keyof ViewportMap | 'responsive' | undefined;

31-31: Make isRotated strictly boolean

You already coerce it to a boolean; reflect that in the prop type.

Apply this diff:

-  isRotated: boolean | undefined;
+  isRotated: boolean;

45-46: Normalize globals shape and annotate viewportName

Explicitly type viewportName to include the sentinel and undefined; coerce isRotated to boolean.

Apply this diff:

-  const viewportName = typeof data === 'string' ? data : data.value;
-  const isRotated = typeof data === 'string' ? false : !!data.isRotated;
+  const viewportName: ViewportKey =
+    typeof data === 'string' ? (data as ViewportKey) : (data?.value as ViewportKey);
+  const isRotated: boolean =
+    typeof data === 'string' ? false : Boolean(data?.isRotated);

Add this supporting type alias near the other types in this file:

type ViewportKey = keyof ViewportMap | 'responsive' | undefined;

48-48: Guard map access when key may be undefined/'responsive'

Avoid indexing with an unvalidated key; fall back to responsiveViewport.

Apply this diff:

-  const item = (options as ViewportMap)[viewportName] || responsiveViewport;
+  const map = options as ViewportMap;
+  const item =
+    viewportName && viewportName !== 'responsive'
+      ? map[viewportName as keyof ViewportMap] ?? responsiveViewport
+      : responsiveViewport;

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

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/essentials/viewport.mdx (1)

171-173: Replace import path with '@storybook/addon-viewport'

docs/essentials/viewport.mdx:171-173 — change
import { INITIAL_VIEWPORTS, MINIMAL_VIEWPORTS } from 'storybook/viewport';
to
import { INITIAL_VIEWPORTS, MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';

🧹 Nitpick comments (1)
docs/essentials/viewport.mdx (1)

59-61: Docs/code mismatch: Nexus 5X height (660 vs 668).

defaults.ts has 412 × 660; docs show 412 × 668. Align docs to code (or vice versa) to avoid confusion.

Apply this diff in the docs to match current code:

-| `nexus5x`        | Nexus 5X                                        | 412 × 668                                                                        |
+| `nexus5x`        | Nexus 5X                                        | 412 × 660                                                                        |
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f5911d and 2416086.

📒 Files selected for processing (2)
  • code/core/src/viewport/defaults.ts (3 hunks)
  • docs/essentials/viewport.mdx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (6)
docs/essentials/viewport.mdx (2)

33-33: Desktop added to MINIMAL_VIEWPORTS looks correct.

Matches code (width × height: 1280 × 1024 shown as 1024 × 1280 in docs). Good.


64-64: Fixed iPad Pro 10.5-in height to 1112 — good catch.

Aligned with defaults.ts.

code/core/src/viewport/defaults.ts (4)

3-3: Good use of “as const satisfies” for precise, readonly types.

This tightens types without runtime impact and preserves literal keys/values. Nice.

Also applies to: 228-228


230-230: Deriving InitialViewportKeys from INITIAL_VIEWPORTS is correct.

Keeps keys in sync with the exported map.


234-267: MINIMAL_VIEWPORTS addition of “desktop” matches docs and types.

Dimensions and type align with the documentation changes.


196-201: Nexus 5X height mismatch — confirm and update

Code: code/core/src/viewport/defaults.ts (nexus5x) sets height '660px' (lines 196–200). Docs: docs/essentials/viewport.mdx lists nexus5x as 412 × 668 (line 59). Update docs to 412 × 660 or change code height to '668px' so they match.

@jonniebigodes
Copy link
Contributor

@hpohlmeyer, it seems there are some issues with this pull request regarding type strictness. Can you check on your end and run some tests to verify what's causing the breakages, adjust accordingly and let us know?

@ndelangen
Copy link
Member

Problems should be resolved now

Copy link
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/viewport/components/Tool.tsx (1)

120-133: Fix ReferenceError: length is undefined in Pure

length is declared in ViewportTool, not in Pure. This will throw at runtime. Compute from viewportMap locally.

 const Pure = React.memo(function PureTool(props: PureProps) {
   const {
     item,
     viewportMap,
     viewportName,
     isRotated,
     updateGlobals,
     setIsTooltipVisible,
     isLocked,
     isActive,
     width,
     height,
   } = props;
 
   const update = useCallback(
     (input: GlobalStateUpdate | undefined) => updateGlobals({ [PARAM_KEY]: input }),
     [updateGlobals]
   );
+
+  const length = Object.keys(viewportMap).length;
🧹 Nitpick comments (1)
code/core/src/viewport/components/Tool.tsx (1)

81-82: Type mismatch: pass a ViewportMap to Pure

PureProps.viewportMap is ViewportMap, but options may be a looser type from useParameter. Cast at the callsite to satisfy the stricter prop.

-        viewportMap: options,
+        viewportMap: options as ViewportMap,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 003012e and c17e0ce.

📒 Files selected for processing (1)
  • code/core/src/viewport/components/Tool.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
PR: storybookjs/storybook#32458
File: code/core/src/viewport/components/Tool.tsx:38-39
Timestamp: 2025-09-18T20:51:06.618Z
Learning: In viewport tool code, when using the `useGlobals` hook from storybook/manager-api, the third returned value `storyGlobals` is guaranteed by TypeScript to be defined (not undefined/null), making the `in` operator safe to use without additional null checks.

Applied to files:

  • code/core/src/viewport/components/Tool.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest

setIsTooltipVisible: React.Dispatch<React.SetStateAction<boolean>>;
viewportMap: ViewportMap;
viewportName: any;
viewportName: keyof ViewportMap;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

viewportName is too strict; allow 'responsive' and undefined

viewportName can be unset or equal to the sentinel 'responsive'. Typing it as keyof ViewportMap introduces type errors and doesn't reflect runtime behavior.

Apply this diff:

-  viewportName: keyof ViewportMap;
+  viewportName: keyof ViewportMap | 'responsive' | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewportName: keyof ViewportMap;
viewportName: keyof ViewportMap | 'responsive' | undefined;
🤖 Prompt for AI Agents
In code/core/src/viewport/components/Tool.tsx around line 28, the prop type
viewportName is too strict as keyof ViewportMap; change its type to allow the
sentinel 'responsive' and undefined (e.g. viewportName?: keyof ViewportMap |
'responsive' or keyof ViewportMap | 'responsive' | undefined) and update any
local usage/guards to handle the 'responsive' and undefined cases (null-checks
or switch/if branches) so the runtime behavior and types align.

Comment on lines 45 to 46
const viewportName = typeof data === 'string' ? data : data.value;
const isRotated: boolean = typeof data === 'string' ? false : data.isRotated;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Normalize globals shape; handle legacy string/empty object safely

globals?.[PARAM_KEY] || {} can produce {}, so data.value/data.isRotated may be undefined. Also, legacy string values may be 'responsive'. Normalize both.

-  const viewportName = typeof data === 'string' ? data : data.value;
-  const isRotated: boolean = typeof data === 'string' ? false : data.isRotated;
+  type ViewportKey = keyof ViewportMap | 'responsive' | undefined;
+  const viewportName: ViewportKey =
+    typeof data === 'string' ? (data as ViewportKey) : (data?.value as ViewportKey);
+  const isRotated = typeof data === 'string' ? false : Boolean(data?.isRotated);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const viewportName = typeof data === 'string' ? data : data.value;
const isRotated: boolean = typeof data === 'string' ? false : data.isRotated;
type ViewportKey = keyof ViewportMap | 'responsive' | undefined;
const viewportName: ViewportKey =
typeof data === 'string' ? (data as ViewportKey) : (data?.value as ViewportKey);
const isRotated = typeof data === 'string' ? false : Boolean(data?.isRotated);
🤖 Prompt for AI Agents
In code/core/src/viewport/components/Tool.tsx around lines 45-46, normalize the
incoming globals shape and handle legacy string/empty-object cases: treat data
that may be a string (including legacy 'responsive') or an object (possibly {}),
coerce it into a safe object with explicit defaults for value and isRotated,
then read viewportName and isRotated from that normalized object; e.g. if typeof
data === 'string' create an object { value: data, isRotated: false }, if data is
null/{} use sensible defaults (value: undefined or a fallback string and
isRotated: false) so accessing .value and .isRotated never yields undefined.

Comment on lines +48 to 49
const item = (options as ViewportMap)[viewportName] || responsiveViewport;
const isActive = isTooltipVisible || item !== responsiveViewport;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard map access when key may be undefined/'responsive'

Indexing with an unvalidated key causes typing friction and possible surprises. Guard and fall back to responsiveViewport.

-  const item = (options as ViewportMap)[viewportName] || responsiveViewport;
+  const map = options as ViewportMap;
+  const item =
+    viewportName && viewportName !== 'responsive'
+      ? map[viewportName as keyof ViewportMap] ?? responsiveViewport
+      : responsiveViewport;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const item = (options as ViewportMap)[viewportName] || responsiveViewport;
const isActive = isTooltipVisible || item !== responsiveViewport;
const map = options as ViewportMap;
const item =
viewportName && viewportName !== 'responsive'
? map[viewportName as keyof ViewportMap] ?? responsiveViewport
: responsiveViewport;
const isActive = isTooltipVisible || item !== responsiveViewport;
🤖 Prompt for AI Agents
In code/core/src/viewport/components/Tool.tsx around lines 48-49, the current
indexing (options as ViewportMap)[viewportName] may access the map with an
undefined or special key ('responsive'); change the logic to guard the lookup
and fall back to responsiveViewport: first test that viewportName is a valid
string key and exists on options (or use a safe optional lookup), then use
options[viewportName] only when that check passes, otherwise set item =
responsiveViewport; adjust types or narrow viewportName as needed so the
compiler knows the key is valid.

@ndelangen ndelangen assigned ndelangen and unassigned jonniebigodes Sep 24, 2025
@ndelangen ndelangen added the core label Sep 24, 2025
@ndelangen ndelangen merged commit 1c283ed into storybookjs:next Sep 24, 2025
54 of 57 checks passed
@github-actions github-actions bot mentioned this pull request Sep 24, 2025
18 tasks
@hpohlmeyer
Copy link
Contributor Author

Sorry, I was swamped with work, thanks for getting it over the finish line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants