Skip to content

Core: Add "open in editor" feature#32452

Merged
yannbf merged 56 commits into
nextfrom
yann/open-in-editor-feature
Sep 18, 2025
Merged

Core: Add "open in editor" feature#32452
yannbf merged 56 commits into
nextfrom
yann/open-in-editor-feature

Conversation

@yannbf
Copy link
Copy Markdown
Member

@yannbf yannbf commented Sep 15, 2025

Closes #

What I did

This PR introduces an API in storybook/manager-api to open a file in the editor:
openInEditor(filePath, lineNumber?, columnNumber?).

This new API is used in a few places to open a story in the editor:

  • Storybook toolbar (in both docs and story) - presented in a new share button
  • Story Context Menu
  • Interactions panel

Such buttons will not be shown in production builds, as this is a dev only feature.

Context Menu

Upon clicking on the three dots in stories only

image

Interactions panel

Presented in local stories only. In composed Storybooks or published Storybooks the text will just be a normal text and not a link
image

New share button

This new button groups the actions of copying the story URL, opening in isolation mode and introduces a QR Code for opening the network address from your phone
image

This PR also fixes an issue where the shortcut font was broken in a few places:
image

How it works

There is a very nice package called launch-editor which comes out of the box with Vite, and once it is present, you can fetch a specific route to open a file in the editor, it will figure out your existing editors and open the most active one, like magic. For Webpack projects, we need to include the launch-editor-middleware, but it works the same way.

Checklist for Contributors

Testing

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

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

Manual testing

  1. Run Storybook either through sandboxes or yarn storybook:ui
  2. Select a story
  3. Click on both open in editor buttons, like the gif above

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 pull request has been released as version 0.0.0-pr-32452-sha-d0653ada. Try it out in a new sandbox by running npx storybook@0.0.0-pr-32452-sha-d0653ada sandbox or in an existing project with npx storybook@0.0.0-pr-32452-sha-d0653ada upgrade.

More information
Published version 0.0.0-pr-32452-sha-d0653ada
Triggered by @yannbf
Repository storybookjs/storybook
Branch yann/open-in-editor-feature
Commit d0653ada
Datetime Wed Sep 17 15:48:54 UTC 2025 (1758124134)
Workflow run 17803236525

To request a new release of this pull request, mention the @storybookjs/core team.

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

Greptile Summary

Updated On: 2025-09-15 08:36:29 UTC

This PR implements an "open in editor" feature that allows developers to quickly open story files directly in their code editor from the Storybook UI. The implementation introduces a new openInEditor(filePath, lineNumber?, columnNumber?) API that leverages the launch-editor ecosystem to automatically detect and open files in the user's preferred editor.

Core Implementation:

  • New API Function: The openInEditor function in code/core/src/manager-api/lib/open-in-editor.ts makes POST requests to a /__open-in-editor endpoint with file path, line, and column information
  • Manager API Integration: The function is exported through the manager-api's public interface and global exports system
  • Builder Support: Webpack5 builder now includes launch-editor-middleware dependency and middleware setup (Vite already has built-in support)

UI Integration Points:

  • Main Toolbar: Added openInEditorTool to the default toolbar tools array, appearing in both story and docs view modes
  • Component Testing: Added an "Open in editor" button to the component testing subnav toolbar
  • Both UI integrations include proper conditional rendering (development mode only, local stories only) and accessibility features

How It Works:
The feature uses the launch-editor package ecosystem which automatically detects available editors and opens the most recently used one. For Vite projects, this works out of the box. For Webpack projects, the PR adds launch-editor-middleware to provide the same functionality. The implementation follows Storybook's established patterns for addon tools and maintains consistency across different UI contexts.

The change integrates seamlessly with Storybook's existing architecture, using the Consumer pattern to access story metadata and following established conventions for toolbar tools and API exports.

Confidence score: 3/5

  • This PR introduces useful functionality but has several implementation concerns that need attention
  • Score reflects potential issues with error handling, TypeScript integration, and missing builder support validation
  • Pay close attention to the Webpack5 builder implementation and the openInEditor function's error handling

Summary by CodeRabbit

  • New Features

    • Share menu in the preview toolbar: copy story link, open in isolation, and QR code for mobile access.
    • Open in editor: toolbar button, preview/context-menu/panel integration, and a new "Open in editor" action.
  • Improvements

    • Copy story name shows "Copied!" feedback and exposes copy-story-link shortcut.
    • Tool layout updated (share + open-in-editor added); tooltips now close only on Escape.
    • Settings label updated; macOS shortcut handling improved.
  • Documentation

    • Updated addon toolkit shortcut example.
  • Chores

    • Dependency bumps and expanded tests (sharing, context menu, settings, clipboard).

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

9 files reviewed, 2 comments

Edit Code Review Bot Settings | Greptile

if (!(storyId && isLocal && importPath)) {
return null;
}
const file = importPath;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: redundant variable assignment - importPath can be used directly instead of assigning to file

Suggested change
const file = importPath;
return (
<IconButton
key="open-in-editor"
onClick={() => openInEditor(importPath)}
title="Open in editor"
aria-label="Open in editor"
>
<VSCodeIcon />
</IconButton>
);

}) => {
const buttonText = status === 'errored' ? 'Scroll to error' : 'Scroll to end';
const theme = useTheme();
const onOpenInEditor = async (file: string | undefined) => openInEditor(file);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: The async function wrapper is unnecessary since openInEditor already returns a Promise and the result isn't awaited

Suggested change
const onOpenInEditor = async (file: string | undefined) => openInEditor(file);
const onOpenInEditor = (file: string | undefined) => openInEditor(file);

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Sep 15, 2025

View your CI Pipeline Execution ↗ for commit cc2ffba

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

☁️ Nx Cloud last updated this comment at 2025-09-18 11:17:12 UTC

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Sep 15, 2025

Package Benchmarks

Commit: cc2ffba, ran on 18 September 2025 at 11:05:45 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 43 43 0
Self size 30.04 MB 30.13 MB 🚨 +94 KB 🚨
Dependency size 17.30 MB 17.30 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 187 187 0
Self size 886 KB 886 KB 0 B
Dependency size 79.64 MB 79.73 MB 🚨 +94 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 169 169 0
Self size 35 KB 35 KB 0 B
Dependency size 76.06 MB 76.16 MB 🚨 +94 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 44 44 0
Self size 1.55 MB 1.55 MB 🚨 +30 B 🚨
Dependency size 47.34 MB 47.43 MB 🚨 +94 KB 🚨
Bundle Size Analyzer node node

@yannbf yannbf force-pushed the yann/open-in-editor-feature branch from 8a3af8f to 5ab5822 Compare September 15, 2025 09:51
… open-in-editor functionality to Subnav and Preview components, allowing users to open files directly in their code editor. Updated package dependencies and added related types for improved type safety.
@yannbf yannbf force-pushed the yann/open-in-editor-feature branch from 0933141 to 1853097 Compare September 15, 2025 15:56
@yannbf yannbf force-pushed the yann/open-in-editor-feature branch from bfb941e to b665876 Compare September 15, 2025 17:32
@yannbf yannbf changed the title WIP - Core: Add "open in editor" feature Core: Add "open in editor" feature Sep 16, 2025
@ghengeveld ghengeveld self-requested a review September 16, 2025 08:09
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

♻️ Duplicate comments (4)
code/e2e-tests/manager.spec.ts (4)

11-15: Grant clipboard permissions before navigation to deflake clipboard assertions

Without explicit permissions, navigator.clipboard.readText() is flaky across browsers. Grant permissions for the Storybook origin before page.goto().

Apply this diff:

   test.beforeEach(async ({ page }) => {
-    await page.goto(storybookUrl);
+    await page.context().grantPermissions(['clipboard-read', 'clipboard-write'], {
+      origin: storybookUrl,
+    });
+    await page.goto(storybookUrl);
 
     await new SbPage(page, expect).waitUntilLoaded();
   });

58-101: Use polling for clipboard; keep context‑menu checks as-is

Great dev-only gating via test.skip. Replace direct clipboard read with expect.poll for stability.

Apply this diff:

-      await expect(page.evaluate(() => navigator.clipboard.readText())).resolves.toContain(
-        'Primary'
-      );
+      await expect
+        .poll(() => page.evaluate(() => navigator.clipboard.readText()))
+        .toContain('Primary');

102-114: Dev share test: poll clipboard result

The dev gating is correct. Swap to polling to avoid timing flakes after clicking “Copy story link”.

Apply this diff:

-      await expect(page.evaluate(() => navigator.clipboard.readText())).resolves.toContain(
-        `${storybookUrl}/?path=/story/example-button--primary`
-      );
+      await expect
+        .poll(() => page.evaluate(() => navigator.clipboard.readText()))
+        .toContain(`${storybookUrl}/?path=/story/example-button--primary`);

116-128: Build share test: poll clipboard result

Same clipboard flake here; use polling.

Apply this diff:

-      await expect(page.evaluate(() => navigator.clipboard.readText())).resolves.toContain(
-        `${storybookUrl}/?path=/story/example-button--primary`
-      );
+      await expect
+        .poll(() => page.evaluate(() => navigator.clipboard.readText()))
+        .toContain(`${storybookUrl}/?path=/story/example-button--primary`);
🧹 Nitpick comments (1)
code/e2e-tests/manager.spec.ts (1)

8-8: Name the env flag more explicitly

Consider renaming type to storybookType for clarity and to avoid confusion with the TS type keyword in declarations.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af37fd7 and cc2ffba.

📒 Files selected for processing (1)
  • code/e2e-tests/manager.spec.ts (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/e2e-tests/manager.spec.ts
🧬 Code graph analysis (1)
code/e2e-tests/manager.spec.ts (1)
code/e2e-tests/util.ts (1)
  • SbPage (8-203)
🔇 Additional comments (6)
code/e2e-tests/manager.spec.ts (6)

20-35: Settings tooltip test reads well

The visibility/hide flows (Escape and outside click) look solid.


49-49: UI trigger switch to Settings: LGTM

Switching the menu trigger to Settings aligns with the new UX.


150-150: Toolbar toggling via Settings: LGTM

Also applies to: 154-154


189-189: Panel toggling via Settings: LGTM


241-241: Fullscreen toggling via Settings: LGTM


269-269: Settings page navigation: LGTM

@yannbf yannbf merged commit 2d630a4 into next Sep 18, 2025
58 checks passed
@yannbf yannbf deleted the yann/open-in-editor-feature branch September 18, 2025 12:10
@github-actions github-actions Bot mentioned this pull request Sep 18, 2025
16 tasks
@ndelangen ndelangen added the needs qa Indicates that this needs manual QA during the upcoming minor/major release label Sep 29, 2025
@ndelangen ndelangen removed the needs qa Indicates that this needs manual QA during the upcoming minor/major release label Oct 1, 2025
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.

3 participants