Skip to content

Actions: Fix state mutation and keep newest actions when limit reached#34286

Merged
Sidnioulz merged 9 commits into
nextfrom
sidnioulz/actions-limit-opencode-test
Apr 3, 2026
Merged

Actions: Fix state mutation and keep newest actions when limit reached#34286
Sidnioulz merged 9 commits into
nextfrom
sidnioulz/actions-limit-opencode-test

Conversation

@Sidnioulz
Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz commented Mar 23, 2026

  • Fix state mutation and retain newest actions when limit reached
  • Add container-level ActionLogger stories with play functions
  • Fix docs: remove limit and clearOnStoryChange from Parameters section (they are configureActions options, already documented in the Exports section)
  • Replace setTimeout(50) with await Promise.resolve() in emitActions for faster, more reliable story tests
  • Fix misleading comment on args.api to accurately describe actual data flow (prop passed via render, not context)

Summary by CodeRabbit

  • New Features

    • ActionLogger now supports configurable action limits with automatic removal of oldest entries when exceeded.
    • Added clearOnStoryChange option to control whether the Actions panel clears during story navigation.
    • Introduced configureActions API for global configuration of Actions settings.
  • Documentation

    • Enhanced Actions documentation with new configuration options and usage examples.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Mar 23, 2026

View your CI Pipeline Execution ↗ for commit e022dd9

Command Status Duration Result
nx run-many -t compile,check,knip,test,lint,fmt... ✅ Succeeded 8m 6s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-02 18:03:07 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eae67270-874d-44cc-99b3-386f6fa427a1

📥 Commits

Reviewing files that changed from the base of the PR and between 4416405 and e022dd9.

📒 Files selected for processing (3)
  • code/.storybook/main.ts
  • code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx
  • code/core/src/actions/containers/ActionLogger/index.tsx
✅ Files skipped from review due to trivial changes (2)
  • code/.storybook/main.ts
  • code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/actions/containers/ActionLogger/index.tsx

📝 Walkthrough

Walkthrough

Adds Storybook discovery for action container stories, introduces ActionLogger stories with play tests and a mock Manager API, refactors ActionLogger to immutably aggregate counts and enforce a configurable per-action limit, and documents new Actions options plus configureActions.

Changes

Cohort / File(s) Summary
Storybook config
code/.storybook/main.ts
Adds a stories entry to include ../core/src/actions/containers with titlePrefix: 'actions'.
ActionLogger stories & tests
code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx
Adds a new Storybook module with a mock Manager API, helper utilities to emit actions, and seven stories with play tests verifying single/repeated/multiple actions, limit enforcement, deduplication counting, and clear behavior.
ActionLogger container logic
code/core/src/actions/containers/ActionLogger/index.tsx
Refactors action handling to compute limit via action.options.limit ?? 50, avoid mutating inputs, immutably replace the last action to increment count, append new actions with count: 1, and uniformly enforce limits using tail-slicing (slice(-limit)).
Documentation
docs/essentials/actions.mdx
Updates frontmatter/formatting; documents new limit (default 50) and clearOnStoryChange (default true) options; adds configureActions(options?: ActionOptions) API and usage example.

Sequence Diagram

sequenceDiagram
    participant Story as Storybook Story
    participant Manager as Manager API
    participant Logger as ActionLogger
    participant State as React State
    participant UI as Rendered UI

    Story->>Manager: emit(action)
    Manager->>Logger: onAction(action)
    Note over Logger: compare incoming action to last stored action
    alt duplicate action (deep equal)
        Logger->>State: replace last action with new object (count + 1)
    else new action
        Logger->>State: append { ...action, count: 1 }
    end
    State->>Logger: actions = actions.slice(-limit)
    Logger->>UI: re-render actions list
    Story->>UI: play() assertions verify rendered output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

🧹 Nitpick comments (2)
code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx (2)

68-74: Consider using waitFor for more robust async handling.

The fixed 50ms setTimeout works but is somewhat fragile—it may be too slow in CI or too fast if React batching changes. Consider using waitFor from the testing library for more deterministic waiting:

+import { waitFor } from 'storybook/test';
+
 async function emitActions(actions: ActionDisplay[]) {
   for (const action of actions) {
     currentApi.emit(EVENT_ID, action);
-    // Let React process the state update
-    await new Promise((resolve) => setTimeout(resolve, 50));
+    // Wait for React to process the state update
+    await waitFor(() => {});
   }
 }

Alternatively, if the current approach works reliably in your test environment, this is a minor concern.

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

In `@code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx`
around lines 68 - 74, The helper emitActions uses a fixed 50ms setTimeout which
is fragile; replace the manual sleep with a deterministic wait using
testing-library's waitFor to await the React updates after each
currentApi.emit(EVENT_ID, action). Update the emitActions function to import and
use waitFor (or waitForNextUpdate) so you call currentApi.emit(EVENT_ID, action)
and then await waitFor(() => /* assertion or state check that indicates the
update applied */) instead of the fixed timeout, referencing the emitActions
function and currentApi.emit to locate where to change.

52-53: Module-level state for test isolation—acceptable pattern but document the constraint.

The module-level currentApi creates implicit coupling between the decorator and play functions. This works because Storybook runs decorators synchronously before render and play. The inline comment at line 52 helps, but consider adding a note that stories should not run in parallel if this ever becomes a concern.

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

In `@code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx`
around lines 52 - 53, Update the existing module-level comment for the
currentApi variable to explicitly document the coupling constraint: explain that
currentApi (type ReturnType<typeof createMockApi>) is intentionally
module-scoped so the decorator and play functions share the same mock, note that
Storybook executes decorators synchronously before render/play, and add a
caution that stories relying on this pattern should not run in parallel to avoid
test isolation issues; reference currentApi, createMockApi, and the
decorator/play functions in the comment so future contributors understand the
limitation and intent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx`:
- Around line 68-74: The helper emitActions uses a fixed 50ms setTimeout which
is fragile; replace the manual sleep with a deterministic wait using
testing-library's waitFor to await the React updates after each
currentApi.emit(EVENT_ID, action). Update the emitActions function to import and
use waitFor (or waitForNextUpdate) so you call currentApi.emit(EVENT_ID, action)
and then await waitFor(() => /* assertion or state check that indicates the
update applied */) instead of the fixed timeout, referencing the emitActions
function and currentApi.emit to locate where to change.
- Around line 52-53: Update the existing module-level comment for the currentApi
variable to explicitly document the coupling constraint: explain that currentApi
(type ReturnType<typeof createMockApi>) is intentionally module-scoped so the
decorator and play functions share the same mock, note that Storybook executes
decorators synchronously before render/play, and add a caution that stories
relying on this pattern should not run in parallel to avoid test isolation
issues; reference currentApi, createMockApi, and the decorator/play functions in
the comment so future contributors understand the limitation and intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bdc6609-b3b3-459b-855a-0f85f4144859

📥 Commits

Reviewing files that changed from the base of the PR and between a5658d3 and c7f720b.

📒 Files selected for processing (4)
  • code/.storybook/main.ts
  • code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx
  • code/core/src/actions/containers/ActionLogger/index.tsx
  • docs/essentials/actions.mdx

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Mar 23, 2026

Package Benchmarks

Commit: e022dd9, ran on 2 April 2026 at 18:04:35 UTC

No significant changes detected, all good. 👏

Copilot AI review requested due to automatic review settings April 1, 2026 10:36
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes Actions panel action-log limiting behavior by preventing React state mutation and ensuring the newest actions are retained when the configured limit is exceeded.

Changes:

  • Avoid mutating previous state objects in the setActions updater and default limit to 50 when missing.
  • Keep the newest action entries when at capacity by switching to slice(-limit).
  • Add container-level stories with play functions to cover limit/clear/dedup behavior and document limit/clearOnStoryChange.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
docs/essentials/actions.mdx Documents limit, clearOnStoryChange, and configureActions for the Actions addon.
code/core/src/actions/containers/ActionLogger/index.tsx Fixes state immutability and retains newest actions when enforcing the limit.
code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx Adds interaction stories validating limit behavior, deduping, and clearing.
code/.storybook/main.ts Registers actions container stories in the repo Storybook config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread code/core/src/actions/containers/ActionLogger/index.tsx Outdated
Comment thread code/.storybook/main.ts Outdated
Comment thread docs/essentials/actions.mdx Outdated
Comment on lines +96 to +114
#### `limit`

Type: `number`

Default: `50`

Maximum number of action entries to display in the actions panel. When the limit is reached, the oldest actions are discarded and the newest are retained.

This parameter is configured globally using the `configureActions` function (see [Exports](#configureactions) below) rather than as a story parameter.

#### `clearOnStoryChange`

Type: `boolean`

Default: `true`

When set to `true`, the actions panel is automatically cleared when navigating to a different story.

This parameter is configured globally using the `configureActions` function (see [Exports](#configureactions) below) rather than as a story parameter.
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The docs list limit and clearOnStoryChange under "Parameters" (parameters.actions.*), but ActionsParameters in code only includes argTypesRegex, disable, handles, and expandLevel (code/core/src/actions/types.ts). Since these options are applied via configureActions/action options (not story parameters read by the addon), consider moving them out of the Parameters section or explicitly labeling them as configureActions options to avoid misleading readers.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

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.

Removed limit and clearOnStoryChange from the Parameters section entirely — they were already fully documented under the configureActions export. Commit 7933cb4.

Comment on lines +66 to +72
* Waits briefly after each emission for React state to settle.
*/
async function emitActions(actions: ActionDisplay[]) {
for (const action of actions) {
currentApi.emit(EVENT_ID, action);
// Let React process the state update
await new Promise((resolve) => setTimeout(resolve, 50));
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

emitActions uses a fixed setTimeout(50) after each emit. This makes the story tests slower and can still be flaky on slower environments. Prefer waiting on UI state (e.g., waitFor / findBy* assertions) after emitting, so the play function synchronizes on actual rendering rather than a hard-coded delay.

Suggested change
* Waits briefly after each emission for React state to settle.
*/
async function emitActions(actions: ActionDisplay[]) {
for (const action of actions) {
currentApi.emit(EVENT_ID, action);
// Let React process the state update
await new Promise((resolve) => setTimeout(resolve, 50));
* Yields to the event loop after each emission so React can process state updates.
* Callers should still synchronize on actual UI state (e.g., via `waitFor` / `findBy*`).
*/
async function emitActions(actions: ActionDisplay[]) {
for (const action of actions) {
currentApi.emit(EVENT_ID, action);
// Yield to allow React to process the state update without relying on a fixed timeout
await Promise.resolve();

Copilot uses AI. Check for mistakes.
Comment thread code/core/src/actions/containers/ActionLogger/ActionLogger.stories.tsx Outdated
Sidnioulz and others added 9 commits April 2, 2026 19:41
…eached

- Stop mutating previous.count and action.count in React state updater
- Use slice(-limit) instead of slice(0, limit) to retain newest actions
- Add fallback default of 50 for undefined limit
Add 7 story variants covering empty state, single/repeated/multiple actions,
limit behavior, scrollable list, and nested data inspection.
Add documentation for the limit parameter (default 50, keeps newest),
clearOnStoryChange parameter (default true), and the configureActions
export function with usage example.
The limit logic lives in the container (addAction callback with
slice(-limit)), not the presentational component. Stories now exercise
the actual container by mocking the manager API as an event emitter
and using play functions to emit actions via EVENT_ID.

New stories test: single action, repeated/deduplicated actions, multiple
actions, limit discarding oldest, limit with deduplication, and clearing.
… fix api comment

Agent-Logs-Url: https://github.com/storybookjs/storybook/sessions/8da2eff4-99ce-48f3-9f8d-a73f45474cfb

Co-authored-by: Sidnioulz <5108577+Sidnioulz@users.noreply.github.com>
@Sidnioulz Sidnioulz force-pushed the sidnioulz/actions-limit-opencode-test branch from 7a4c87e to e022dd9 Compare April 2, 2026 17:52
@Sidnioulz Sidnioulz merged commit 181be22 into next Apr 3, 2026
123 of 125 checks passed
@Sidnioulz Sidnioulz deleted the sidnioulz/actions-limit-opencode-test branch April 3, 2026 09:48
@github-actions github-actions Bot mentioned this pull request Apr 3, 2026
10 tasks
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