Skip to content

Vue: Make globals reactive in decorators#34116

Open
Sidnioulz wants to merge 1 commit intonextfrom
reapply-vue-globals-reactivity-fix
Open

Vue: Make globals reactive in decorators#34116
Sidnioulz wants to merge 1 commit intonextfrom
reapply-vue-globals-reactivity-fix

Conversation

@Sidnioulz
Copy link
Member

@Sidnioulz Sidnioulz commented Mar 12, 2026

This reverts commit 07f07ec.

Checklist for Contributors

Testing

Manual testing

See #33562

🦋 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

    • Added support for reactive global state management in Vue 3 decorators
    • Enabled dynamic story argument updates within decorators using updateArgs
  • Tests

    • Added comprehensive end-to-end test suite for Vue 3 decorators with reactive functionality
    • Expanded unit test coverage for reactive global state handling
  • Documentation

    • Added Vue 3 examples and guides for decorators with reactive globals and updateArgs patterns

@Sidnioulz Sidnioulz self-assigned this Mar 12, 2026
@Sidnioulz Sidnioulz added bug ci:daily Run the CI jobs that normally run in the daily job. labels Mar 12, 2026
@nx-cloud
Copy link

nx-cloud bot commented Mar 12, 2026

View your CI Pipeline Execution ↗ for commit 1761296

Command Status Duration Result
nx run-many -t compile -c production --parallel=1 ✅ Succeeded 5m 21s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-12 15:29:59 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Implements reactive globals support in the Vue 3 renderer by making the updateArgs API generic and tracking globals state reactively alongside args. Adds decorators and stories demonstrating updateArgs and reactive globals functionality, includes end-to-end tests, and documentation with code snippet examples.

Changes

Cohort / File(s) Summary
Vue 3 Renderer Core
code/renderers/vue3/src/render.ts, code/renderers/vue3/src/render.test.ts
Reworked updateArgs to a generic form updateArgs<T>, added reactiveGlobals state management to track globals reactively, and introduced test coverage for reactive globals updates via computed watchers.
Vue 3 Template Stories
code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts
Introduced updateArgsDecorator for incrementing label via button clicks and localeDecorator for rendering locale-aware greetings based on globals. Added UpdateArgs and ReactiveGlobalDecorator stories showcasing the new decorator patterns.
End-to-End Tests
code/e2e-tests/framework-vue3.spec.ts
Added Playwright test suite validating updateArgs functionality in decorators and reactive globals consumption via toolbar locale changes.
Documentation
docs/writing-stories/decorators.mdx, docs/_snippets/decorator-with-reactive-globals.md, docs/_snippets/decorator-with-updateArgs.md
Added Vue-specific documentation sections explaining reactive globals and preview API hooks with code snippet examples demonstrating decorator patterns for both updateArgs and reactive globals use cases.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Decorator
    participant Globals State
    participant Computed Value
    participant Vue Component

    User->>Decorator: Change locale via toolbar
    Decorator->>Globals State: Update globals.locale
    Globals State->>Computed Value: Trigger reactivity
    Computed Value->>Computed Value: Recompute greeting based on locale
    Computed Value->>Vue Component: Notify of change
    Vue Component->>Vue Component: Re-render with new locale attribute & greeting
    Vue Component-->>User: Display updated greeting in Spanish/English
Loading
sequenceDiagram
    actor User
    participant Decorator
    participant Args State
    participant Vue Component

    User->>Decorator: Click "Add 1" button
    Decorator->>Args State: Call updateArgs to increment label
    Args State->>Args State: Update reactive args.label
    Args State->>Vue Component: Notify of change
    Vue Component->>Vue Component: Re-render with new label value
    Vue Component-->>User: Display incremented counter (0→1→2)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
📝 Coding Plan for PR comments
  • Generate coding plan

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

🤖 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/renderers/vue3/src/render.ts`:
- Around line 156-158: The early return when Object.keys(nextArgs).length === 0
prevents the deletion pass from running and leaves stale keys in
reactiveArgs/reactiveGlobals; remove the early return and ensure the existing
deletion loop (the code that iterates reactiveArgs and reactiveGlobals and
deletes keys not present in nextArgs) always runs even if nextArgs is empty so
that both reactiveArgs and reactiveGlobals are fully cleared when nextArgs is
{}.

In `@docs/_snippets/decorator-with-reactive-globals.md`:
- Around line 14-19: The Vue template uses mustache interpolation inside an
attribute (the lang attribute in the template string) which is invalid; update
both template snippets to use a v-bind on the lang attribute so it receives a
JavaScript expression that uses globals?.locale with a fallback of 'en' (i.e.,
bind the lang attribute rather than using {{ }}); locate the template blocks
containing the lang attribute and replace the attribute usage accordingly in
both places.

In `@docs/_snippets/decorator-with-updateArgs.md`:
- Around line 1-28: The snippet defines WithIncrementDecorator but never exports
it, so Storybook won't register the decorator; update the snippet to export the
decorator properly by converting the constant into a preview export (for example
create a default export matching Storybook's Preview shape that includes
decorators or export a named "decorators" array referencing the decorator),
ensure the decorator uses useArgs()/updateArgs as shown (keep the function body
and template) and remove the unused WithIncrementDecorator constant or replace
it with an exported value so Storybook's preview file receives either a default
export or named export (e.g., export const decorators = [...]) that includes the
decorator.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78fe1373-fa2b-4531-9969-fc07a16f1fb4

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba2d07 and 1761296.

📒 Files selected for processing (7)
  • code/e2e-tests/framework-vue3.spec.ts
  • code/renderers/vue3/src/render.test.ts
  • code/renderers/vue3/src/render.ts
  • code/renderers/vue3/template/stories_vue3-vite-default-ts/decorators.stories.ts
  • docs/_snippets/decorator-with-reactive-globals.md
  • docs/_snippets/decorator-with-updateArgs.md
  • docs/writing-stories/decorators.mdx

Comment on lines 156 to 158
if (Object.keys(nextArgs).length === 0) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't skip the delete pass when nextArgs is empty.

Returning here leaves every stale key in reactiveArgs / reactiveGlobals intact, so resetting args or clearing toolbar globals back to {} keeps the old values alive in Vue decorators. This should still run the deletion loop and end up with an empty reactive object.

💡 Proposed fix
 export function updateArgs<
   T extends {
     [name: string]: unknown;
   },
 >(reactiveArgs: T, nextArgs: T) {
-  if (Object.keys(nextArgs).length === 0) {
-    return;
-  }
   const currentArgs = isReactive(reactiveArgs) ? reactiveArgs : reactive(reactiveArgs);
   // delete all args in currentArgs that are not in nextArgs
   Object.keys(currentArgs).forEach((key) => {
     if (!(key in nextArgs)) {
       delete currentArgs[key];
📝 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
if (Object.keys(nextArgs).length === 0) {
return;
}
const currentArgs = isReactive(reactiveArgs) ? reactiveArgs : reactive(reactiveArgs);
// delete all args in currentArgs that are not in nextArgs
Object.keys(currentArgs).forEach((key) => {
if (!(key in nextArgs)) {
delete currentArgs[key];
}
});
// add/update args from nextArgs
Object.keys(nextArgs).forEach((key) => {
currentArgs[key] = nextArgs[key];
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/renderers/vue3/src/render.ts` around lines 156 - 158, The early return
when Object.keys(nextArgs).length === 0 prevents the deletion pass from running
and leaves stale keys in reactiveArgs/reactiveGlobals; remove the early return
and ensure the existing deletion loop (the code that iterates reactiveArgs and
reactiveGlobals and deletes keys not present in nextArgs) always runs even if
nextArgs is empty so that both reactiveArgs and reactiveGlobals are fully
cleared when nextArgs is {}.

Comment on lines +14 to +19
template: `
<div :lang={{globals?.locale || 'en'}}>
<p>Greeting: {{greeting}}</p>
<story />
</div>
`,
Copy link
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

🌐 Web query:

In Vue 3 templates, is :lang={{globals?.locale || 'en'}}valid syntax, or should the binding be written as:lang="globals?.locale || 'en'"?

💡 Result:

:lang={{globals?.locale || 'en'}} is not valid Vue 3 template syntax—mustache interpolation ({{ }}) can’t be used inside HTML attributes. Use a v-bind / : binding with a JavaScript expression instead:

:lang="globals?.locale || 'en'"

This is exactly what Vue’s template syntax docs describe for attribute bindings. [1]

Sources
[1] Vue.js Guide — Template Syntax (Attribute Bindings): (vuejs.org)

Citations:


Fix the :lang binding syntax in both Vue snippets.

:lang={{globals?.locale || 'en'}} is not valid Vue template syntax—mustache interpolation ({{ }}) cannot be used inside HTML attributes. Use a v-bind binding with a JavaScript expression instead: :lang="globals?.locale || 'en'".

💡 Proposed fix
         template: `
-          <div :lang={{globals?.locale || 'en'}}>
+          <div :lang="globals?.locale || 'en'">
             <p>Greeting: {{greeting}}</p>
             <story />
           </div>
         `,

Also applies to lines 40-45.

📝 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
template: `
<div :lang={{globals?.locale || 'en'}}>
<p>Greeting: {{greeting}}</p>
<story />
</div>
`,
template: `
<div :lang="globals?.locale || 'en'">
<p>Greeting: {{greeting}}</p>
<story />
</div>
`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/_snippets/decorator-with-reactive-globals.md` around lines 14 - 19, The
Vue template uses mustache interpolation inside an attribute (the lang attribute
in the template string) which is invalid; update both template snippets to use a
v-bind on the lang attribute so it receives a JavaScript expression that uses
globals?.locale with a fallback of 'en' (i.e., bind the lang attribute rather
than using {{ }}); locate the template blocks containing the lang attribute and
replace the attribute usage accordingly in both places.

Comment on lines +1 to +28
```js filename=".storybook/preview.js" renderer="vue" language="js"
import { useArgs } from 'storybook/preview-api';

const WithIncrementDecorator = {
args: {
counter: 0,
},
decorators: [
(story, { args }) => {
const [, updateArgs] = useArgs();
return {
components: { story },
setup() {
return { args, updateArgs };
},
template: `
<div>
<button @click="() => updateArgs({ counter: args.counter + 1 })">
Increment
</button>
<story />
</div>
`,
};
},
],
};
```
Copy link
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

🌐 Web query:

In Storybook Vue 3, should .storybook/preview.tsexport a defaultPreviewobject, and would a file that only containsconst WithIncrementDecorator = { args, decorators } be treated as valid preview configuration?

💡 Result:

Yes—.storybook/preview.ts should export Storybook “project annotations”, and the currently recommended/typed pattern is a default export of a Preview object (e.g. const preview: Preview = { decorators, parameters, args, ... }; export default preview;). [1]

Storybook can also read specific named exports (e.g. export const decorators = [...]) from preview.ts, which it turns into internal configuration. [2]

A file that only contains:

const WithIncrementDecorator = { args, decorators };

is not valid preview configuration by itself, because nothing is exported for Storybook to consume. To make it effective, you’d need to export it (either as the default preview object or by exporting the relevant named fields). [1][2][3]

Sources:
[1] Storybook docs – Setup / .storybook/preview.ts default Preview export pattern (storybook.js.org)
[2] Storybook docs – Builder API (preview config via named exports like decorators) (storybook.js.org)
[3] Storybook docs – Global args configured via default export in preview.js|ts (storybook.js.org)

Citations:


Snippet filename does not match the code pattern—no exports.

The code defines WithIncrementDecorator as a constant but exports nothing. Storybook's .storybook/preview.ts requires either a default export of a Preview object or named exports (e.g., export const decorators = [...]). Pasting this code into a preview file will not register the decorator. Either show this as a *.stories.* example with an exported story, or convert it to a proper preview default export.

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

In `@docs/_snippets/decorator-with-updateArgs.md` around lines 1 - 28, The snippet
defines WithIncrementDecorator but never exports it, so Storybook won't register
the decorator; update the snippet to export the decorator properly by converting
the constant into a preview export (for example create a default export matching
Storybook's Preview shape that includes decorators or export a named
"decorators" array referencing the decorator), ensure the decorator uses
useArgs()/updateArgs as shown (keep the function body and template) and remove
the unused WithIncrementDecorator constant or replace it with an exported value
so Storybook's preview file receives either a default export or named export
(e.g., export const decorators = [...]) that includes the decorator.

@storybook-app-bot
Copy link

Package Benchmarks

Commit: 1761296, ran on 12 March 2026 at 15:25:37 UTC

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

@storybook/builder-webpack5

Before After Difference
Dependency count 185 185 0
Self size 76 KB 76 KB 0 B
Dependency size 32.18 MB 32.19 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

storybook

Before After Difference
Dependency count 49 49 0
Self size 20.21 MB 20.21 MB 🚨 +1 KB 🚨
Dependency size 16.52 MB 16.54 MB 🚨 +17 KB 🚨
Bundle Size Analyzer Link Link

@storybook/angular

Before After Difference
Dependency count 185 185 0
Self size 140 KB 140 KB 0 B
Dependency size 30.39 MB 30.40 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 189 189 0
Self size 15 KB 15 KB 🚨 +18 B 🚨
Dependency size 28.89 MB 28.91 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 534 534 0
Self size 650 KB 650 KB 🎉 -120 B 🎉
Dependency size 59.89 MB 59.91 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 92 92 0
Self size 1.12 MB 1.12 MB 0 B
Dependency size 22.45 MB 22.47 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 124 124 0
Self size 30 KB 30 KB 0 B
Dependency size 23.74 MB 23.75 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 82 82 0
Self size 35 KB 35 KB 🚨 +18 B 🚨
Dependency size 20.24 MB 20.25 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 271 271 0
Self size 24 KB 24 KB 🎉 -12 B 🎉
Dependency size 44.52 MB 44.53 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/server-webpack5

Before After Difference
Dependency count 197 197 0
Self size 16 KB 16 KB 0 B
Dependency size 33.44 MB 33.45 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 183 183 0
Self size 779 KB 779 KB 0 B
Dependency size 67.35 MB 67.38 MB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 176 176 0
Self size 32 KB 32 KB 0 B
Dependency size 65.88 MB 65.91 MB 🚨 +32 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 50 50 0
Self size 1.04 MB 1.04 MB 0 B
Dependency size 36.73 MB 36.75 MB 🚨 +18 KB 🚨
Bundle Size Analyzer node node

@storybook/preset-react-webpack

Before After Difference
Dependency count 167 167 0
Self size 18 KB 18 KB 0 B
Dependency size 31.39 MB 31.40 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react

Before After Difference
Dependency count 58 58 0
Self size 1.19 MB 1.19 MB 🚨 +6 B 🚨
Dependency size 13.19 MB 13.20 MB 🚨 +14 KB 🚨
Bundle Size Analyzer Link Link

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

Labels

bug ci:daily Run the CI jobs that normally run in the daily job.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant