Skip to content

Conversation

@ghengeveld
Copy link
Member

@ghengeveld ghengeveld commented Nov 26, 2025

What I did

  • Updated useChecklist to add ready state in order to prevent updating state while not ready.
  • Disable ChecklistWidget animations until settled down to prevent a late layout shift.
  • Only delay transition for outgoing items, to improve perceived performance
  • Persist ChecklistWidget collapsible state in sessionStorage.

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

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

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-33184-sha-43ff7fbc. Try it out in a new sandbox by running npx [email protected] sandbox or in an existing project with npx [email protected] upgrade.

More information
Published version 0.0.0-pr-33184-sha-43ff7fbc
Triggered by @ghengeveld
Repository storybookjs/storybook
Branch improve-checklist-widget-transitions
Commit 43ff7fbc
Datetime Wed Nov 26 11:14:17 UTC 2025 (1764155657)
Workflow run 19701792760

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 publish.yml --field pr=33184

Summary by CodeRabbit

  • New Features

    • Checklist exposes a ready flag and a readiness/init flow to delay rendering/subscriptions until prepared.
    • Collapsible now supports initialCollapsed and persistent state (storageKey) so collapse state can be remembered.
  • Refactor

    • Two-phase, staged item rendering for smoother exit/entry transitions and conditional animation timing.
    • Improved per-item status handling and guarded rendering to prevent unwanted transitions.

✏️ Tip: You can customize this high-level summary in your review settings.

… animations until settled down and only delay transition for outgoing items
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 26, 2025

📝 Walkthrough

Walkthrough

Adds a staged two‑phase render and animation gating to the checklist widget, readiness/init gating in the checklist hook, and persistent collapsible state support; animations are deferred until a debounced ready signal and store READY status, enabling exit transitions before new items appear.

Changes

Cohort / File(s) Summary
Checklist animation & render flow
code/core/src/manager/components/sidebar/ChecklistWidget.tsx
Adds ready and animated flags, staged renderItems initialization from nextItems, per-item animateOut tracking, two-phase render (preserve items for exit, then swap), conditional transition timeout (300ms when animated, 0ms otherwise), 1s delay before enabling animated, and particle icon shown only when isCompleted && animated.
Readiness & initialization hook
code/core/src/manager/components/sidebar/useChecklist.ts
Adds initialized and ready state, imports PREVIEW_INITIALIZED and debounce, introduces a 500ms debounced ready signal, gates subscribe/effects on universal store READY status, registers/unregisters PREVIEW_INITIALIZED listener, and returns ready: boolean from the hook.
Collapsible persistence & API
code/core/src/components/components/Collapsible/Collapsible.tsx
Adds initialCollapsed and storageKey props, replaces boolean state with session/storage-backed state when storageKey present via useSessionState, refactors useCollapsible to an object-argument signature ({ collapsed, disabled, initialCollapsed, storageKey }), and syncs prop changes and toggles with storage persistence.
Manifest
package.json
(Listed as changed in manifest block) — no API surface changes indicated; likely dependency or metadata tweak.

Sequence Diagram(s)

sequenceDiagram
    participant Hook as useChecklist
    participant Store as UniversalStore
    participant Widget as ChecklistWidget
    rect `#EBF3FF`
      note over Hook,Store: Initialization & readiness gating
      Hook->>Store: observe status
      Store-->>Hook: status != READY
      Hook->>Hook: ready = false
    end

    rect `#EDFFED`
      note over Store,Hook: Store becomes READY + PREVIEW_INITIALIZED
      Store->>Hook: PREVIEW_INITIALIZED
      Hook->>Hook: initialized = true
      Hook->>Hook: debounceReady() (500ms)
      Hook-->>Widget: ready = true
    end

    rect `#FFF5EB`
      note over Widget,Hook: Checklist two‑phase animation flow
      Widget->>Widget: delay 1s -> animated = true
      alt not animated
        Widget->>Widget: render nextItems as gate (no transitions, timeout=0ms)
      else animated
        Widget->>Widget: Phase A: keep current items, set per-item animateOut -> allow exit (2s)
        Widget->>Widget: Phase B (after exit): set item flags (isCompleted/isAccepted/isDone/isSkipped), render nextItems (transition timeout 300ms)
        Widget->>Widget: show particle icon only if isCompleted && animated
      end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on:
    • ChecklistWidget.tsx: timing/ordering of setTimeouts, correct lifecycle of renderItemsState and per-item animateOut, and transition timeout branching.
    • useChecklist.ts: debounce correctness, PREVIEW_INITIALIZED listener lifecycle, and dependency arrays (avoid missed/unnecessary re-runs).
    • Collapsible.tsx & useCollapsible: storage key naming, session persistence correctness, and backward compatibility with prior prop usage.
    • Any interactions between readiness flag and component render paths to avoid race conditions.

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

🧹 Nitpick comments (1)
code/core/src/manager/components/sidebar/useChecklist.ts (1)

116-121: Good addition of readiness gating with debounce.

The state initialization and debounce setup look correct. However, the debounce function created via useMemo is not cancelled on component unmount, which could cause a state update on an unmounted component.

Add cleanup for the debounce function:

+  useEffect(() => {
+    return () => debounceReady.cancel();
+  }, [debounceReady]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa3ef6e and 43ff7fb.

📒 Files selected for processing (2)
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx (2 hunks)
  • code/core/src/manager/components/sidebar/useChecklist.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use camelCase for variable and function names

Files:

  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
  • code/core/src/manager/components/sidebar/useChecklist.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
  • code/core/src/manager/components/sidebar/useChecklist.ts
**/*.{ts,tsx,js,jsx,json,html,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
  • code/core/src/manager/components/sidebar/useChecklist.ts
code/**/!(*.test).{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
  • code/core/src/manager/components/sidebar/useChecklist.ts
🧠 Learnings (6)
📚 Learning: 2025-11-05T09:38:47.712Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.

Applied to files:

  • code/core/src/manager/components/sidebar/useChecklist.ts
📚 Learning: 2025-09-24T09:39:39.233Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.

Applied to files:

  • code/core/src/manager/components/sidebar/useChecklist.ts
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/viewport/components/Tool.tsx:38-39
Timestamp: 2025-09-18T20:51:06.618Z
Learning: The useGlobals hook from storybook/manager-api returns a tuple where the third element (storyGlobals) is typed as Globals, not Globals | undefined. This means TypeScript guarantees it's always defined, making the `in` operator safe to use without additional null checks.

Applied to files:

  • code/core/src/manager/components/sidebar/useChecklist.ts
📚 Learning: 2025-09-18T20:51:06.618Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 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/manager/components/sidebar/useChecklist.ts
📚 Learning: 2025-11-05T09:37:25.920Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/tooltip/WithTooltip.tsx:54-96
Timestamp: 2025-11-05T09:37:25.920Z
Learning: Repo: storybookjs/storybook — In code/core/src/components/components/tooltip/WithTooltip.tsx, the legacy WithTooltip implementation is intentionally reintroduced for backward compatibility and is deprecated; maintainers (per Sidnioulz) do not want maintenance or improvements on it. Prefer WithTooltipNew/Popover; avoid suggesting changes to WithTooltip.* going forward.

Applied to files:

  • code/core/src/manager/components/sidebar/useChecklist.ts
📚 Learning: 2025-11-24T17:49:47.659Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T17:49:47.659Z
Learning: The Storybook repository is a TypeScript/React monorepo with main codebase in 'code/' directory and tooling in 'scripts/' directory

Applied to files:

  • code/core/src/manager/components/sidebar/useChecklist.ts
🧬 Code graph analysis (2)
code/core/src/manager/components/sidebar/ChecklistWidget.tsx (1)
code/core/src/manager/components/sidebar/useChecklist.ts (2)
  • useChecklist (111-246)
  • ChecklistItem (30-41)
code/core/src/manager/components/sidebar/useChecklist.ts (2)
code/core/src/manager-api/stores/checklist.ts (1)
  • universalChecklistStore (9-12)
code/core/src/manager-api/index.ts (1)
  • universalChecklistStore (23-23)
⏰ 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
🔇 Additional comments (8)
code/core/src/manager/components/sidebar/useChecklist.ts (3)

195-225: LGTM for subscription gating logic.

The addition of status !== experimental_UniversalStore.Status.READY check correctly prevents subscriptions from being set up before the universal store is ready, which aligns with the PR objective of preventing premature state updates.


239-245: LGTM for the extended return value.

The ready boolean is correctly added to the return object, enabling downstream components like ChecklistWidget to gate their animations.


227-231: I'll help you verify this review comment about the event listener cleanup. Let me investigate how the api.once and api.off methods are implemented.
<function_calls>


#!/bin/bash

First, let's locate the manager-api files to understand the api implementation

find . -type f -name "managerapi*" | grep -E ".(ts|tsx|js)$" | head -20


</function_calls>

Let me search for the API implementation more broadly:
<function_calls>


#!/bin/bash

Search for the once and off method implementations

rg -n ".once\s*(" --type ts -B 2 -A 5 | head -50


</function_calls>

Let me look at the actual file and understand what api object is being used:
<function_calls>


#!/bin/bash

Read the useChecklist.ts file to understand the context

cat -n code/core/src/manager/components/sidebar/useChecklist.ts | head -50


</function_calls>

Let me get more of the file to see the imports:
<function_calls>


#!/bin/bash

Get line count and read the beginning of the file

wc -l code/core/src/manager/components/sidebar/useChecklist.ts
head -40 code/core/src/manager/components/sidebar/useChecklist.ts


</function_calls>

Now let me search for the manager-api implementation:
<function_calls>


#!/bin/bash

Find manager-api related files

find . -type d -name "manager-api" 2>/dev/null
find . -path "managerapi*" -name ".ts" -o -name ".tsx" 2>/dev/null | grep -v node_modules | head -20


</function_calls>

Let me search for the StoryBookAPI or similar class that would have the once and off methods:
<function_calls>


#!/bin/bash

Search for class or interface definitions containing 'once' method

rg -n "^\s*(class|interface|type|export).*API" --type ts code/ | grep -i manager | head -20


</function_calls>

Let me look at the specific lines in question first:
<function_calls>


#!/bin/bash

Get lines 220-235 from the file

sed -n '220,235p' code/core/src/manager/components/sidebar/useChecklist.ts


</function_calls>

Let me also get the imports section:
<function_calls>


#!/bin/bash

Get the imports and declarations at the top

head -100 code/core/src/manager/components/sidebar/useChecklist.ts | tail -60


</function_calls>

Let me search for where the api object comes from in this file:
<function_calls>


#!/bin/bash

Search for api variable usage in the file

rg -n "api\s*(=|:)" code/core/src/manager/components/sidebar/useChecklist.ts -B 2 -A 2


</function_calls>

<function_calls>


#!/bin/bash

Let's look at the full file to understand context

cat code/core/src/manager/components/sidebar/useChecklist.ts | head -150


</function_calls>

<function_calls>


#!/bin/bash

Search for addons API or manager API definitions

find code -type f -name "*.ts" -path "api" ! -path "node_modules" | head -20


</function_calls>

Let me search for the once and off implementations more directly:
<function_calls>


#!/bin/bash

Search for once method definition (not usage)

rg -n "^\sonce\s(" --type ts -B 3 -A 8 | head -80


</function_calls>

<function_calls>


#!/bin/bash

Search for Channel or EventEmitter-like patterns

rg -n "(class|export).*Channel" --type ts | head -20


</function_calls>

<function_calls>


#!/bin/bash

Search for methods named 'once' and 'off' in TypeScript files

rg -n "off\s*(" --type ts -B 5 | grep -A 5 "^\soff\s(" | head -50


</function_calls>

Let me try a different approach and search in the API directory:
<function_calls>


#!/bin/bash

Look for addons or manager API implementations

fd "addons" --type d code/ | head -5


</function_calls>

<function_calls>


#!/bin/bash

Search for EventEmitter or similar patterns

rg -n "EventEmitter|on\s*(|once\s*(" --type ts code/ -l | head -10


</function_calls>

Let me check the addons API structure:
<function_calls>


#!/bin/bash

Find API-related files in addons

find code -type f -path "addons" -name "*.ts" | grep -i api | head -20


</function_calls>

<function_calls>


#!/bin/bash

Let's search in lib or shared directories

find code -type d ( -name "lib" -o -name "shared" -o -name "api" ) | head -10


</function_calls>

Let me look at what directory structure exists:
<function_calls>


#!/bin/bash

List top-level directories in code/

ls -la code/ | head -30


</function_calls>

<function_calls>


#!/bin/bash

Search for the api parameter usage pattern

rg -n "api.(once|off)" --type ts -B 2 -A 2 code/ | head -60


</function_calls>

Now let me look for test files that might show how these methods work:
<function_calls>


#!/bin/bash

Search for tests related to once/off

fd ".*.test.(ts|tsx)" code/ -x rg -l "once|off" {} ; | head -10


</function_calls>

<function_calls>


#!/bin/bash

Let's look for the specific implementation of event listeners

rg -n "export.*class.*Api" --type ts code/ | head -20


</function_calls>

Let me check the addon-api directory specifically:
<function_calls>


#!/bin/bash

Find addon-api

find code -type d -name "addon" | head -10


</function_calls>

<function_calls>


#!/bin/bash

Search for addons.api or manager.api imports

rg -n "from.*addon.*api|from.*manager.*api" --type ts code/ | head -20


</function_calls>

<function_calls>


#!/bin/bash

Find where PREVIEW_INITIALIZED is defined

rg -n "PREVIEW_INITIALIZED" --type ts code/ | head -10


</function_calls>

Now let me search for the actual implementation by looking at @storybook packages:
<function_calls>


#!/bin/bash

Look at the package structure

find code -type f -name "package.json" | xargs grep -l "storybook/internal" 2>/dev/null | head -5


</function_calls>

<function_calls>


#!/bin/bash

Search for EventEmitter implementation or Channel class

rg -n "class.*Channel|class.*EventEmitter" --type ts code/ -B 2 -A 15 | head -100


</function_calls>

Let me search for the specific addons API implementation:
<function_calls>


#!/bin/bash

Search more broadly for "once" implementations

ast-grep --pattern 'once($, $) { $$$ }'


</function_calls>

<function_calls>


#!/bin/bash

Search for function definitions of once

rg -n "^\s+(once|off)\s*(" --type ts -A 5 code/ | head -100


</function_calls>

Let me try to find the Channel implementation:
<function_calls>


#!/bin/bash

Look in the entire codebase for Channel-like classes

rg -n "class Channel" --type ts -A 20 code/ | head -150


</function_calls>

<function_calls>


#!/bin/bash

Search for on, once, off in object literals or class definitions

rg -n "once\s*[:=].*()" --type ts -B 2 -A 5 code/ | head -100


</function_calls>

Let me look at the internal storybook packages:
<function_calls>


#!/bin/bash

Find files in internal packages

find code -path "internal" -name "*.ts" | grep -i "api|channel|event" | head -20


</function_calls>

<function_calls>


#!/bin/bash

Search more carefully for the api object methods

rg -n ".once(" code/core/src/manager/components/sidebar/useChecklist.ts -B 5 -A 3


</function_calls>

<function_calls>


#!/bin/bash

Let's check if it's using a standard EventEmitter pattern

rg -n "EventEmitter" --type ts code/ -B 2 -A 10 | head -100


</function_calls>

Let me try searching in shared or lib directories more systematically:
<function_calls>


#!/bin/bash

Look for addons API directory more carefully

ls -la code/core/src/addons/ 2>/dev/null || ls -la code/core/src/addon* 2>/dev/null || find code/core/src -maxdepth 2 -type d -name "addon"


</function_calls>

<function_calls>


#!/bin/bash

Search for where the api comes from in useChecklist

rg -n "useContext.*API|useAddonState|useManager" code/core/src/manager/components/sidebar/useChecklist.ts -B 2 -A 2


</function_calls>

Let me get the full file content to understand the context:
<function_calls>


#!/bin/bash

Get the entire file

cat code/core/src/manager/components/sidebar/useChecklist.ts


</function_calls>

Perfect! Now let me look for the ManagerContext to understand where the api comes from:
<function_calls>


#!/bin/bash

Search for ManagerContext definition

rg -n "ManagerContext" code/ --type ts -B 2 -A 5 | head -50


</function_calls>

<function_calls>


#!/bin/bash

Find the manager context file

find code -type f -name "managercontext*" -o -name "context" | grep manager


</function_calls>

<function_calls>


#!/bin/bash

Search for manager API or provider

rg -n "createContext.*api|ManagerApi|useApi" code/ --type ts | head -20


</function_calls>

Let me search for where the api.once and api.off methods are actually used together:
<function_calls>


#!/bin/bash

Look for all usages of api.once and api.off

rg -n "api.(once|off)" code/ --type ts -B 1 -A 1


</function_calls>

Now let me check what type of API object this is - search for the actual implementation:
<function_calls>


#!/bin/bash

Search for API-related interfaces or types

rg -n "interface.*API|type.*API" code/ --type ts | grep -v ".test" | head -30


</function_calls>

<function_calls>


#!/bin/bash

Let's look for the ManagerApi specifically

find code -type f -name "*.ts" | xargs grep -l "class ManagerApi|interface ManagerApi" 2>/dev/null | head -5


</function_calls>

<function_calls>


#!/bin/bash

Search in the manager directory structure

find code/core/src/manager -type f -name "*.ts" | head -30


</function_calls>

<function_calls>


#!/bin/bash

Look for index or provider files in manager

ls -la code/core/src/manager/*.ts 2>/dev/null || ls -la code/core/src/manager/


</function_calls>

<function_calls>


#!/bin/bash

Check for the provider or main manager files

find code/core/src -name "provider" -o -name "manager.ts" | head -20


</function_calls>

Let me look for the ManagerProvider specifically:
<function_calls>


#!/bin/bash

Search for ManagerProvider

rg -n "ManagerProvider|export.*Manager" code/core/src/manager --type ts -A 3 | head -80


</function_calls>

<function_calls>


#!/bin/bash

Let's examine the manager directory structure more carefully

find code/core/src/manager -type f -name "*.ts" -exec basename {} ; | sort | uniq


</function_calls>

<function_calls>


#!/bin/bash

Find the provider file

find code/core/src/manager -name "rovider" -o -name "index.ts"


</function_calls>

<function_calls>


#!/bin/bash

List all files in manager directory

find code/core/src/manager -type f -name ".ts" -o -name ".tsx"


</function_calls>

<function_calls>


#!/bin/bash

Read one of the manager files to understand the pattern

ls code/core/src/manager/.ts


</function_calls>

<function_calls>


#!/bin/bash

Get more details about directory structure

ls -1 code/core/src/manager/


</function_calls>

<function_calls>


#!/bin/bash

Look for ManagerProvider.tsx

cat code/core/src/manager/ManagerProvider.tsx 2>/dev/null | head -100


</function_calls>

<function_calls>


#!/bin/bash

Search in internal/addons or api modules

find code -path "/internal/" -name "*.ts" | xargs grep -l "once.*off|Channel" 2>/dev/null | head -10


</function_calls>

Let me search for the actual Channel implementation across the whole repo:
<function_calls>


#!/bin/bash

Search for Channel class

rg -n "export.*class Channel" code/ --type ts -A 30 | head -150


</function_calls>

<function_calls>


#!/bin/bash

Look for the implementation with a different approach - search for the pattern of method definitions

rg -n "^\s+(once|off)\s*(.)\s{" code/ --type ts -A 3 | head -100


</function_calls>

Let me try searching in different packages:
<function_calls>


#!/bin/bash

Search for API implementations using different pattern

python3 << 'EOF'
import subprocess
import json

Search for files that have both "once" and "off" methods

result = subprocess.run(
['rg', '-l', 'once', '--type', 'ts', 'code/'],
capture_output=True,
text=True
)

files_with_once = result.stdout.strip().split('\n')

for file in files_with_once[:20]:
if file:
# Check if file also has 'off' method
result2 = subprocess.run(
['rg', '-n', 'off\s*(|once\s*(', file],
capture_output=True,
text=True
)
if 'off' in result2.stdout and 'once' in result2.stdout:
print(f"Found in: {file}")
EOF


</function_calls>

<function_calls>


#!/bin/bash

Let's use a simpler approach - look for where the methods are defined together

rg -n -A 15 "^\sonce\s(" code/ --type ts | grep -B 15 -A 5 "off\s*(" | head -100


</function_calls>

Let me search more broadly for the EventEmitter-like pattern:
<function_calls>


#!/bin/bash

Search for common event emitter patterns

rg -n "listeners.*Map|_listeners|eventHandlers" code/ --type ts -B 3 -A 3 | head -80


</function_calls>

<function_calls>


#!/bin/bash

Let's check in core/src itself for api-related files

find code/core/src -maxdepth 2 -type f -name "*.ts" | xargs grep -l "once|off" | head -20


</function_calls>

<function_calls>


#!/bin/bash

Find all TypeScript files in code/core/src and search for the pattern

find code/core/src -type f -name ".ts" -o -name ".tsx" | xargs grep -l ".once\s*(" | head -10


</function_calls>

<function_calls>


#!/bin/bash

Look for the actual API type being used

rg -n "api:\s*" code/core/src/manager/components/sidebar/useChecklist.ts -A 1


</function_calls>

Let me check what the useContext returns:
<function_calls>


#!/bin/bash

Look for ManagerContext type or provider

rg -n "ManagerContext" code/core/src --type ts -B 5 -A 10 | head -100


</function_calls>

Let me try a web search to understand the pattern better, but first let me check if there are any comments or tests showing this pattern:
<function_calls>


#!/bin/bash

Search for test files that use api.once/off

find code -name ".test.ts" -o -name ".spec.ts" | xargs grep -l "api.(once|off)" 2>/dev/null | head -5


</function_calls>

<function_calls>


#!/bin/bash

Look at what ManagerProvider.tsx contains to understand the api type

head -200 code/core/src/manager/ManagerProvider.tsx


</function_calls>

Great! Now let me look for the API implementation:
<function_calls>


#!/bin/bash

Search for where the api object is created or what type it is

rg -n "new.*Api|createApi|makeApi" code/core/src/manager --type ts -B 2 -A 5 | head -50


</function_calls>

<function_calls>


#!/bin/bash

Look for AddonStore or API manager

rg -n "class.*Store|export.*Store" code/ --type ts | grep -i addon | head -10


</function_calls>

Let me search for the Channel implementation in addons API:
<function_calls>


#!/bin/bash

Search for Channel implementation more carefully

find code -type f -name "*.ts" | xargs grep -n "export class Channel|export.*Channel" 2>/dev/null | head -10


</function_calls>

<function_calls>


#!/bin/bash

Let's look at the internal storybook packages

find code -path "/internal/" -type f -name "*.ts" | head -20


</function_calls>

Let me examine one of the internal files:
<function_calls>


#!/bin/bash

Find and check key files

find code -path "internal" -name "*.ts" -exec rg -l "once|Channel" {} ; | head -5


</function_calls>

<function_calls>


#!/bin/bash

Search in storybook related files for the implementation

rg -n "once\s*:" code/ --type ts -B 3 -A 8 | head -100


</function_calls>

Let me check the storybook package folders structure:
<function_calls>


#!/bin/bash

List storybook packages

ls -la code/


</function_calls>

<function_calls>


#!/bin/bash

Check what's in the lib folder if it exists

ls -la code/lib 2>/dev/null || ls -la code/


</function_calls>

Let me look more carefully at what's imported in the manager files:
<function_calls>


#!/bin/bash

Search for all imports in ManagerProvider

cat code/core/src/manager/ManagerProvider.tsx | head -50


</function_calls>

<function_calls>


#!/bin/bash

Let's look at what API is - search for where it comes from

grep -n "import.*api|from.*api" code/core/src/manager/ManagerProvider.tsx


</function_calls>

<function_calls>


#!/bin/bash

Find the createApi function

rg -n "createApi|new API|new.*Api" code/core/src/manager --type ts -B 2 -A 10 | head -100


</function_calls>

<function_calls>


#!/bin/bash

Look for the API implementation in core

ls -la code/core/src/api/ 2>/dev/null || find code/core/src -maxdepth 2 -type d -name "api"


</function_calls>

<function_calls>


#!/bin/bash

Check what exists in code/core/src

ls -la code/core/src/


</function_calls>

<function_calls>


#!/bin/bash

Look in core for api files

find code/core/src -name "api" -type f


</function_calls>

Let me check the addons folder:
<function_calls>


#!/bin/bash

Look for addons API

ls -la code/core/src/addons/ 2>/dev/null || find code -type d -name "addon"


</function_calls>

<function_calls>


#!/bin/bash

Search for where AddonsStore or similar is imported

rg -n "import.*Store|from.*store" code/core/src/manager/ManagerProvider.tsx


</function_calls>

Let me check if AddonsStore is the one being used:
<function_calls>


#!/bin/bash

Find AddonsStore implementation

rg -n "class AddonsStore|export.*AddonsStore" code/ --type ts -A 30 | head -150


</function_calls>

Great! Now let me look at the full AddonsStore implementation to understand the once/off methods:
<function_calls>


#!/bin/bash

Get the AddonsStore file location and examine it

find code -name "AddonsStore" -o -name "addonsstore*" | head -5


</function_calls>

<function_calls>


#!/bin/bash

Let's find the file containing AddonsStore

rg -n "class AddonsStore" code/ --type ts -l


</function_calls>

<function_calls>


#!/bin/bash

Get the full AddonsStore implementation

rg -n "class AddonsStore" code/ --type ts -A 200 | head -250


</function_calls>

Perfect! Now I can see the AddonsStore implementation. Let me focus on the once and off methods:
<function_calls>


#!/bin/bash

Get the complete AddonsStore to understand once/off behavior

rg -n "class AddonsStore" code/ --type ts -A 300 | grep -A 300 "class AddonsStore" | head -350


</function_calls>

<function_calls>


#!/bin/bash

Let's get the file path first

rg -n "class AddonsStore" code/ --type ts -l


</function_calls>

<function_calls>


#!/bin/bash

Read the complete file containing AddonsStore

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)
wc -l "$file"
cat -n "$file"


</function_calls>

Excellent! Now I have the full AddonsStore implementation. Let me analyze the once and off methods more carefully:
<function_calls>


#!/bin/bash

Extract just the once and off methods from AddonsStore

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)
sed -n '/^\sonce(/,/^\s}/p' "$file" | head -30


</function_calls>

<function_calls>


#!/bin/bash

Get the off method as well

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)
sed -n '/^\soff(/,/^\s}/p' "$file" | head -30


</function_calls>

Perfect! Now let me verify my understanding by checking if there are any related test or documentation:
<function_calls>


#!/bin/bash

Search for tests using api.once and api.off together

rg -n "api.once.*api.off|api.off.*once" code/ --type ts -B 3 -A 3 | head -60


</function_calls>

<function_calls>


#!/bin/bash

Check if there are other usages of this pattern

rg -n ".once(.*initialize)" code/ --type ts -B 2 -A 3


</function_calls>

<function_calls>


#!/bin/bash

Look for other cleanup patterns with once

rg -n "useEffect.*once|once.*api." code/ --type ts -B 2 -A 5 | head -80


</function_calls>

Now let me verify my understanding of the listener management:
<function_calls>


#!/bin/bash

Check if listeners are properly tracked

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)

Extract the listeners property/field

cat -n "$file" | sed -n '/listeners\s*=/,/;/p' | head -10


</function_calls>

<function_calls>


#!/bin/bash

Get better understanding of the on, once, off implementation

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)

Show the relevant methods

awk '/^\s*(on|once|off)\s*(/{flag=1} flag{print NR": "$0} /^\s*}/{if(flag) flag=0}' "$file" | head -100


</function_calls>

Let me read the file more carefully to understand the complete implementation:
<function_calls>


#!/bin/bash

Get the actual file and look at lines around the methods

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)

Find line numbers for the methods

line_on=$(grep -n "^\son(" "$file" | head -1 | cut -d: -f1)
line_once=$(grep -n "^\s
once(" "$file" | head -1 | cut -d: -f1)
line_off=$(grep -n "^\s*off(" "$file" | head -1 | cut -d: -f1)
echo "on at line: $line_on"
echo "once at line: $line_once"
echo "off at line: $line_off"

Show each method

if [ ! -z "$line_on" ]; then
echo "=== on method ==="
sed -n "${line_on},$((line_on+15))p" "$file"
fi

if [ ! -z "$line_once" ]; then
echo "=== once method ==="
sed -n "${line_once},$((line_once+15))p" "$file"
fi

if [ ! -z "$line_off" ]; then
echo "=== off method ==="
sed -n "${line_off},$((line_off+15))p" "$file"
fi


</function_calls>

Perfect! Now I have all the information I need to verify the review comment. Let me analyze the findings:

Now let me verify if there are test cases or documentation about this:
<function_calls>


#!/bin/bash

Search for tests of the AddonsStore to understand expected usage

find code -name ".test.ts" -o -name ".spec.ts" | xargs grep -l "AddonsStore|api.once|api.off" 2>/dev/null | head -5


</function_calls>

<function_calls>


#!/bin/bash

Look for any JSDoc or comments explaining the expected behavior

rg -n "once|off" code/core/src/addons/index.ts -B 3 | head -80


</function_calls>

<function_calls>


#!/bin/bash

Check if there's documentation about proper cleanup pattern

rg -n "cleanup|useEffect" code/core/src/addons/index.ts -B 2 -A 2


</function_calls>

Now let me verify my conclusion by testing the actual behavior:
<function_calls>


#!/bin/bash

Let's verify the exact implementation once more to be absolutely certain

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)
cat "$file" | python3 << 'PYEOF'
import sys
content = sys.stdin.read()

Find once method

import re
once_match = re.search(r'once\s*(\sevent\s:\sstring[^{]{([^}](?:{[^}]}[^}]))}', content, re.DOTALL)
off_match = re.search(r'off\s*(\sevent\s:\sstring[^{]{([^}](?:{[^}]}[^}]))}', content, re.DOTALL)

if once_match:
print("=== once method ===")
print(once_match.group(0)[:300])
print()

if off_match:
print("=== off method ===")
print(off_match.group(0)[:300])
PYEOF


</function_calls>

Let me take a simpler approach and just verify the key finding:
<function_calls>


#!/bin/bash

Verify the critical difference: once wraps the listener

file=$(rg -n "class AddonsStore" code/ --type ts -l | head -1)

Show the once method - it wraps the callback

echo "=== Key finding: once() wraps the listener ==="
sed -n '/_once/,/^ *}/p' "$file" | head -20

echo ""
echo "=== Key finding: off() uses reference equality ==="

Show the off method - it looks for exact reference match

sed -n '/^ *off(/,/^ *}/p' "$file" | head -20


</function_calls>

Based on my thorough investigation, I can now provide the final rewritten review comment:

The api.off call will not remove listeners registered with api.once if the event hasn't fired yet.

The AddonsStore implementation shows that api.once() wraps the provided callback in a separate wrapper function before adding it to the listeners array. When api.off() is called with the original initialize function, it searches for that exact reference in the listeners array. Since once() added a wrapper (not the original function), off() won't find or remove it, leaving the wrapped listener registered. While this doesn't cause a memory leak in practice, it means the cleanup isn't functioning as intended. Consider using api.on() with manual cleanup or storing the wrapper function for proper cleanup.

code/core/src/manager/components/sidebar/ChecklistWidget.tsx (5)

177-178: Potential issue with initial renderItems state.

useState<ChecklistItem[]>(nextItems) captures nextItems only at initial render. If nextItems is empty on first render but populated later, renderItems won't update until the effect on lines 188-211 runs. This is likely intentional given the PR's goal of preventing layout shifts, but be aware that renderItems may be empty initially even when nextItems has values from the second render onward.

Is this intentional behavior? The initial render will always show no items until effects run, which could cause a brief flash if nextItems is immediately available.


180-186: LGTM for animation delay gating.

The 1-second delay after ready becomes true effectively prevents animations during the initial settling period. The cleanup correctly clears the timeout on unmount or if ready changes.


188-211: Two-phase animation logic is well-structured.

The logic correctly handles:

  1. Immediate updates when not animated
  2. Preserving outgoing items with updated status for exit transitions
  3. 2-second delay before showing new items

One minor observation: the animateOut calculation accumulates across all items, so any completed/skipped item triggers the delay. This appears intentional for batch transitions.


213-217: Good conditional timeout for transition animations.

Setting timeout: 0 when !animated ensures immediate rendering without transition delays during the initial load, improving perceived performance as intended by the PR.


311-315: LGTM for conditional particle animation.

The particle effect is correctly gated behind animated to prevent premature visual effects. The StatusFailIcon fallback ensures a consistent visual state during initial render.

@nx-cloud
Copy link

nx-cloud bot commented Nov 26, 2025

View your CI Pipeline Execution ↗ for commit 56da180

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

☁️ Nx Cloud last updated this comment at 2025-11-26 15:54:02 UTC

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

🧹 Nitpick comments (1)
code/core/src/components/components/Collapsible/Collapsible.tsx (1)

71-89: Consider guarding key access before parsing.

The double non-null assertion key! on line 74 is a code smell. While the try-catch handles undefined keys, the intent is unclear. Consider explicitly checking key before accessing sessionStorage:

 const useSessionState = <T,>(key: string | undefined, initialValue: T) => {
   const [value, setValue] = useState<T>(() => {
+    if (!key) return initialValue;
     try {
-      return (JSON.parse(sessionStorage.getItem(key!)!) as T) ?? initialValue;
+      const stored = sessionStorage.getItem(key);
+      return stored ? (JSON.parse(stored) as T) : initialValue;
     } catch {
       return initialValue;
     }
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b777f4 and 8ee2b2b.

📒 Files selected for processing (2)
  • code/core/src/components/components/Collapsible/Collapsible.tsx (3 hunks)
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use camelCase for variable and function names

Files:

  • code/core/src/components/components/Collapsible/Collapsible.tsx
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Enable TypeScript strict mode
Export functions from modules for testing purposes

Files:

  • code/core/src/components/components/Collapsible/Collapsible.tsx
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
**/*.{ts,tsx,js,jsx,json,html,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx,json,html,mjs}: Use ESLint and Prettier for code style enforcement
Run 'yarn prettier --write ' to format code after making changes
Run 'yarn lint:js:cmd ' to check for ESLint issues after making changes

Files:

  • code/core/src/components/components/Collapsible/Collapsible.tsx
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
code/**/!(*.test).{ts,tsx,js,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/!(*.test).{ts,tsx,js,mjs}: Use 'logger' from 'storybook/internal/node-logger' for server-side (Node.js) logging, not console.log/console.warn/console.error
Use 'logger' from 'storybook/internal/client-logger' for client-side (browser) logging, not console.log/console.warn/console.error
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/components/components/Collapsible/Collapsible.tsx
  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-05T09:38:47.712Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.

Applied to files:

  • code/core/src/manager/components/sidebar/ChecklistWidget.tsx
🧬 Code graph analysis (1)
code/core/src/manager/components/sidebar/ChecklistWidget.tsx (1)
code/core/src/manager/components/sidebar/useChecklist.ts (2)
  • useChecklist (111-250)
  • ChecklistItem (30-41)
⏰ 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
🔇 Additional comments (7)
code/core/src/components/components/Collapsible/Collapsible.tsx (2)

91-105: LGTM on the hook signature refactor.

The object parameter pattern for useCollapsible is cleaner and more extensible. The conditional storage key prefixing with useCollapsible: provides good namespacing for sessionStorage entries.


107-121: LGTM on the effect and toggle logic.

The effect correctly syncs external collapsed prop changes, and toggleCollapsed properly respects the disabled state. Dependency arrays are correct.

code/core/src/manager/components/sidebar/ChecklistWidget.tsx (5)

176-178: LGTM on state initialization.

The renderItems state correctly initializes from nextItems, and the animated flag gates transitions until the widget is ready. This aligns with the PR objective to avoid late layout shifts.


180-186: LGTM on animation delay effect.

The 1-second delay after ready becomes true ensures checklist items have settled before enabling animations. Cleanup function properly clears the timeout.


213-217: LGTM on transition array configuration.

The conditional timeout (animated ? 300 : 0) ensures no animation delay during initial render, preventing layout shifts. The hasItems derived state simplifies subsequent logic.


222-224: LGTM on Collapsible integration.

The storageKey="checklist-widget" enables persistent collapsed state across sessions, and initialCollapsed={!hasItems} provides a sensible default based on content availability.


311-316: LGTM on conditional Particles animation.

Showing the Particles animation only when item.isCompleted && animated prevents visual glitches during the initial render phase before the widget has settled.

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.

2 participants