Skip to content

Actions: Warn instead of throw for implicit actions in docs renders#34505

Open
EricMChavez wants to merge 1 commit intostorybookjs:nextfrom
EricMChavez:fix/33758-docs-implicit-actions
Open

Actions: Warn instead of throw for implicit actions in docs renders#34505
EricMChavez wants to merge 1 commit intostorybookjs:nextfrom
EricMChavez:fix/33758-docs-implicit-actions

Conversation

@EricMChavez
Copy link
Copy Markdown

@EricMChavez EricMChavez commented Apr 9, 2026

Closes #33758

What I did

When a story is embedded in a docs page (viewMode: 'docs') and an arg matched by actions.argTypesRegex (e.g. ^on.*) fires during render — for example a Vue 3 component emitting an event from onMounted — the implicit action handler currently throws ImplicitActionsDuringRendering, which crashes the entire docs page.

The throw exists to protect play functions from accidentally relying on implicit actions, but docs renders have no play-function semantics. This PR downgrades the throw to a console.warn when the active StoryRender has viewMode === 'docs'. Story view (viewMode === 'story') is unchanged, so play-function safety is preserved.

The discriminator is reliable: docs-embedded stories are mounted via Preview.renderStoryToElement, which hard-codes 'docs' as the StoryRender view mode, and ViewMode is exhaustively 'story' | 'docs'.

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

New unit tests in code/core/src/actions/runtime/action.test.ts:

  • viewMode: 'story' + phase: 'rendering' → throws (preserves play-function safety)
  • viewMode: 'docs' + phase: 'rendering' → warns, no throw
  • no active render → no-op

Manual testing

  1. Use the reporter's repro: https://repro-storybook-implicit-action.vercel.app/
  2. Reproduce locally with a Vue 3 sandbox:
    • yarn task sandbox --template vue3-vite/default-ts --start-from auto
    • Add a component that emits an event from onMounted and a story with an onBlabla argType (no matching args entry).
    • Open the story's docs page; before this fix it crashes with ImplicitActionsDuringRendering, after this fix the docs page renders and the warning shows in the console.

Documentation

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

This is a behavior fix, not a deprecation — no migration entry added. Happy to add a note if reviewers prefer.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added test suite for implicit action behavior across different story render contexts.
  • Bug Fixes

    • Implicit actions in docs view mode now warn instead of throwing errors, improving the docs rendering experience.

Stories embedded in a docs page render with viewMode === 'docs' and have
no play function semantics, so an implicit action firing during a
lifecycle hook (e.g. Vue 3 onMounted emitting an event) should warn
rather than crash the entire docs page.

Fixes storybookjs#33758

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

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Changes update implicit-action error handling in Storybook's action handler. When stories render in docs mode (viewMode: 'docs'), implicit actions now always produce warnings instead of potentially throwing errors. New tests verify behavior across story mode, docs mode, and scenarios with no active render.

Changes

Cohort / File(s) Summary
Implicit Action Tests
code/core/src/actions/runtime/action.test.ts
New Vitest suite covering implicit actions during story rendering with three test cases: error on active story render, warning on active docs render, and no error when no render is active. Mocks Storybook preview API and global state.
Implicit Action Runtime Logic
code/core/src/actions/runtime/action.ts
Updated error/warning determination for implicit actions. In docs-mode rendering (viewMode: 'docs'), ImplicitActionsDuringRendering is now always treated as deprecated (warning) regardless of globalThis.FEATURES.disallowImplicitActionsInRenderV8.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes


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.

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: 2

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/actions/runtime/action.ts (1)

67-80: ⚠️ Potential issue | 🟠 Major

Use Storybook client logger instead of console.warn in runtime code.

Line 79 uses raw console.warn in a client-side runtime code path. Replace it with storybook/internal/client-logger for consistency with the repository's logging standards.

Proposed fix
 import { ImplicitActionsDuringRendering } from 'storybook/internal/preview-errors';
+import { logger } from 'storybook/internal/client-logger';
 import type { Renderer } from 'storybook/internal/types';
@@
         if (deprecated) {
-          console.warn(error);
+          logger.warn(error);
         } else {
           throw error;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/runtime/action.ts` around lines 67 - 80, Replace the
raw console.warn call with Storybook's client logger: import the logger from
'storybook/internal/client-logger' and call its warn method with the
ImplicitActionsDuringRendering error instead of console.warn; update the block
where inDocs/deprecated and error (created from ImplicitActionsDuringRendering
using storyRenderer.phase and name) are defined to use clientLogger.warn(error)
so runtime logging follows the repo standard.
🧹 Nitpick comments (1)
code/core/src/actions/runtime/action.test.ts (1)

12-13: Restore global keys by original presence, not only original value.

Lines 20-21 always reassign properties, which can leave keys present with undefined even if they were absent originally. That can affect later tests that use 'key' in global checks.

Proposed fix
 describe('action handler — implicit actions', () => {
+  const hadPreview = '__STORYBOOK_PREVIEW__' in (global as any);
+  const hadFeatures = 'FEATURES' in (globalThis as any);
   const originalPreview = (global as any).__STORYBOOK_PREVIEW__;
   const originalFeatures = (globalThis as any).FEATURES;
@@
   afterEach(() => {
-    (global as any).__STORYBOOK_PREVIEW__ = originalPreview;
-    (globalThis as any).FEATURES = originalFeatures;
+    if (hadPreview) {
+      (global as any).__STORYBOOK_PREVIEW__ = originalPreview;
+    } else {
+      delete (global as any).__STORYBOOK_PREVIEW__;
+    }
+    if (hadFeatures) {
+      (globalThis as any).FEATURES = originalFeatures;
+    } else {
+      delete (globalThis as any).FEATURES;
+    }
     vi.restoreAllMocks();
   });

Also applies to: 19-22

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/runtime/action.test.ts` around lines 12 - 13, The test
saves originalPreview and originalFeatures but restores them unconditionally,
which can leave properties set to undefined when they were originally absent;
update the teardown to record presence (e.g., hadPreview/hadFeatures) when
capturing (global as any).__STORYBOOK_PREVIEW__ and (globalThis as any).FEATURES
and then on restore set the property back only if it was originally present
(assign originalPreview/originalFeatures) otherwise delete the property (use
delete (global as any).__STORYBOOK_PREVIEW__ and delete (globalThis as
any).FEATURES) so key presence matches original state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/core/src/actions/runtime/action.test.ts`:
- Line 5: Change the relative import in action.test.ts to include the explicit
.ts extension: locate the import statement "import { action } from './action';"
and update it to import from './action.ts' so it follows the repository's import
rule; ensure any other imports in this test file follow the same explicit .ts
extension convention.
- Around line 7-9: The test uses vi.mock('storybook/preview-api') without spy:
true and has an inline vi.spyOn(...) inside a test; update the module mock to
vi.mock('storybook/preview-api', { spy: true }, ...) and move any inline
vi.spyOn(...) mockImplementation calls out of the test into the test suite's
beforeEach so all mock behaviors for the file (action.test.ts) are declared in
beforeEach; ensure the spy created via vi.mock supports spyOn and the prior
inline mockImplementation is replicated in that beforeEach setup.

---

Outside diff comments:
In `@code/core/src/actions/runtime/action.ts`:
- Around line 67-80: Replace the raw console.warn call with Storybook's client
logger: import the logger from 'storybook/internal/client-logger' and call its
warn method with the ImplicitActionsDuringRendering error instead of
console.warn; update the block where inDocs/deprecated and error (created from
ImplicitActionsDuringRendering using storyRenderer.phase and name) are defined
to use clientLogger.warn(error) so runtime logging follows the repo standard.

---

Nitpick comments:
In `@code/core/src/actions/runtime/action.test.ts`:
- Around line 12-13: The test saves originalPreview and originalFeatures but
restores them unconditionally, which can leave properties set to undefined when
they were originally absent; update the teardown to record presence (e.g.,
hadPreview/hadFeatures) when capturing (global as any).__STORYBOOK_PREVIEW__ and
(globalThis as any).FEATURES and then on restore set the property back only if
it was originally present (assign originalPreview/originalFeatures) otherwise
delete the property (use delete (global as any).__STORYBOOK_PREVIEW__ and delete
(globalThis as any).FEATURES) so key presence matches original state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9c3dc644-3264-4743-9082-a60e99d976e9

📥 Commits

Reviewing files that changed from the base of the PR and between 7022915 and 61c4c0a.

📒 Files selected for processing (2)
  • code/core/src/actions/runtime/action.test.ts
  • code/core/src/actions/runtime/action.ts


import { global } from '@storybook/global';

import { action } from './action';
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Locate the test file and check its contents around line 5
fd -t f "action.test.ts" code/core/src/actions/runtime/

Repository: storybookjs/storybook

Length of output: 109


🏁 Script executed:

# Read the test file to verify the import statement
head -n 15 code/core/src/actions/runtime/action.test.ts

Repository: storybookjs/storybook

Length of output: 519


🏁 Script executed:

# Check if the target module (action.ts) exists in the same directory
ls -la code/core/src/actions/runtime/ | grep -E "^-.*action\.(ts|tsx|js|jsx)$"

Repository: storybookjs/storybook

Length of output: 123


Add explicit .ts extension for the relative import.

Line 5 should use an explicit extension (./action.ts) to match repository import rules.

Proposed fix
-import { action } from './action';
+import { action } from './action.ts';
📝 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
import { action } from './action';
import { action } from './action.ts';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/runtime/action.test.ts` at line 5, Change the relative
import in action.test.ts to include the explicit .ts extension: locate the
import statement "import { action } from './action';" and update it to import
from './action.ts' so it follows the repository's import rule; ensure any other
imports in this test file follow the same explicit .ts extension convention.

Comment on lines +7 to +9
vi.mock('storybook/preview-api', () => ({
addons: { getChannel: () => ({ emit: vi.fn() }) },
}));
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n code/core/src/actions/runtime/action.test.ts | head -50

Repository: storybookjs/storybook

Length of output: 2103


Align Vitest mocks with the repo's spy-mocking conventions.

Lines 7-9 use vi.mock() without the spy: true option, and line 39 has an inline vi.spyOn() mock implementation inside a test case. Both violate the coding guidelines:

  • "Use vi.mock() with the spy: true option for all package and file mocks"
  • "Avoid inline mock implementations within test cases"
  • "Implement mock behaviors in beforeEach blocks"
Proposed refactor
+import { addons } from 'storybook/preview-api';
 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
 
 import { global } from '@storybook/global';
 
 import { action } from './action';
 
-vi.mock('storybook/preview-api', () => ({
-  addons: { getChannel: () => ({ emit: vi.fn() }) },
-}));
+vi.mock('storybook/preview-api', { spy: true });
 
 describe('action handler — implicit actions', () => {
   const originalPreview = (global as any).__STORYBOOK_PREVIEW__;
   const originalFeatures = (globalThis as any).FEATURES;
+  const mockChannel = { emit: vi.fn() };
+  let warnSpy: ReturnType<typeof vi.spyOn>;
 
   beforeEach(() => {
     (globalThis as any).FEATURES = { disallowImplicitActionsInRenderV8: true };
+    vi.mocked(addons.getChannel).mockReturnValue(mockChannel as any);
+    warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
   });
 
   // ... other test setup ...
 
   it('warns instead of throwing when the render is in docs viewMode', () => {
     setRender({ phase: 'rendering', viewMode: 'docs' });
-    const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
     const handler = action('onBlabla', { implicit: true });
     expect(() => handler()).not.toThrow();
-    expect(warn).toHaveBeenCalled();
+    expect(warnSpy).toHaveBeenCalled();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/actions/runtime/action.test.ts` around lines 7 - 9, The test
uses vi.mock('storybook/preview-api') without spy: true and has an inline
vi.spyOn(...) inside a test; update the module mock to
vi.mock('storybook/preview-api', { spy: true }, ...) and move any inline
vi.spyOn(...) mockImplementation calls out of the test into the test suite's
beforeEach so all mock behaviors for the file (action.test.ts) are declared in
beforeEach; ensure the spy created via vi.mock supports spyOn and the prior
inline mockImplementation is replicated in that beforeEach setup.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: "Implicit actions can not be used" error when defining argTypes with on without handler in doc file

1 participant