Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
## CES Info

### Group Members

| Student | ID |
| --------------------- | --------- |
| Beatriz Oziel de Lima | 202510621 |
| Rubens Brock Silva | 202510624 |
| Matvii Suk | 202514197 |

### Issues

| Issue | Resources |
| --------------------------------------------------------------- | ----------------------------------- |
| [#34258](https://github.com/storybookjs/storybook/issues/34258) | [Report doc](issue-34258-report.md) |
| [#34566](https://github.com/storybookjs/storybook/issues/34566) | [Report doc](issue-34566-report.md) |

Comment on lines +1 to +17

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 | ⚡ Quick win

Remove assignment-specific PII from the root README

Line 5–9 publishes personal student identifiers in a project-wide document. This should not live in the public repository README; keep assignment metadata outside the product docs.

Suggested change
-## CES Info
-
-### Group Members
-
-| Student               | ID        |
-| --------------------- | --------- |
-| Beatriz Oziel de Lima | 202510621 |
-| Rubens Brock Silva    | 202510624 |
-| Matvii Suk            | 202514197 |
-
-### Issues
-
-| Issue                                                           | Resources                           |
-| --------------------------------------------------------------- | ----------------------------------- |
-| [`#34258`](https://github.com/storybookjs/storybook/issues/34258) | [Report doc](issue-34258-report.md) |
-| [`#34566`](https://github.com/storybookjs/storybook/issues/34566) | [Report doc](issue-34566-report.md) |
-
-## Rest of the Original README
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 1 - 17, The README currently exposes
assignment-specific PII in the "Group Members" table (student names and IDs);
remove or anonymize the ID column and any student identifiers from the root
README and instead relocate full member details to a non-public/internal
metadata file or private storage; update the "Group Members" section to use
initials or first names only (or simply list roles) and keep the Issues table
intact; ensure references to the removed info are not left in other top-level
docs and commit the change with a clear message.

## Rest of the Original README

<p align="center">
<a href="https://storybook.js.org/?ref=readme">
<picture>
Expand Down
10 changes: 7 additions & 3 deletions code/core/src/core-server/presets/common-override-preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ export const framework: PresetProperty<'framework'> = async (config) => {
};

export const stories: PresetProperty<'stories'> = async (entries, options) => {
const resolvedEntries = typeof entries === 'function'
? await entries()
: entries;

if (options?.build?.test?.disableMDXEntries) {
return removeMDXEntries(entries, options);
return removeMDXEntries(resolvedEntries, options);
}
return entries;

return resolvedEntries;
};

export const typescript: PresetProperty<'typescript'> = async (input, options) => {
if (options?.build?.test?.disableDocgen) {
return { ...(input ?? {}), reactDocgen: false, check: false };
Expand Down
9 changes: 9 additions & 0 deletions code/core/src/manager-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum Category {
MANAGER_CORE_EVENTS = 'MANAGER_CORE-EVENTS',
MANAGER_ROUTER = 'MANAGER_ROUTER',
MANAGER_THEMING = 'MANAGER_THEMING',
MANAGER_UNIVERSAL_STORE = 'MANAGER_UNIVERSAL-STORE',
}

export class ProviderDoesNotExtendBaseProviderError extends StorybookError {
Expand Down Expand Up @@ -47,6 +48,14 @@ export class UncaughtManagerError extends StorybookError {
}
}

export {
UniversalStoreFollowerTimeoutError,
UniversalStoreIdRequiredError,
UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError,
UniversalStoreNotReadyError,
} from './shared/universal-store/errors';

export class StatusTypeIdMismatchError extends StorybookError {
constructor(
public data: {
Expand Down
5 changes: 5 additions & 0 deletions code/core/src/manager/globals/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,11 @@ export default {
'ProviderDoesNotExtendBaseProviderError',
'StatusTypeIdMismatchError',
'UncaughtManagerError',
'UniversalStoreFollowerTimeoutError',
'UniversalStoreIdRequiredError',
'UniversalStoreMissingSubscribeArgumentError',
'UniversalStoreNotConstructableError',
'UniversalStoreNotReadyError',
],
'storybook/internal/router': [
'BaseLocationProvider',
Expand Down
60 changes: 60 additions & 0 deletions code/core/src/shared/universal-store/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { StorybookError } from '../../storybook-error';

export abstract class UniversalStoreError extends StorybookError {
constructor(props: { code: number; message: string; name: string }) {
super({
...props,
category: 'MANAGER_UNIVERSAL-STORE',
});
}
}

export class UniversalStoreFollowerTimeoutError extends UniversalStoreError {
constructor(public data: { id: string }) {
super({
name: 'UniversalStoreFollowerTimeoutError',
code: 1,
message: `No existing state found for follower with id: '${data.id}'. Make sure a leader with the same id exists before creating a follower.`,
});
}
}

export class UniversalStoreNotConstructableError extends UniversalStoreError {
constructor() {
super({
name: 'UniversalStoreNotConstructableError',
code: 1001,
message: 'UniversalStore is not constructable - use UniversalStore.create() instead',
});
}
}

export class UniversalStoreIdRequiredError extends UniversalStoreError {
constructor() {
super({
name: 'UniversalStoreIdRequiredError',
code: 1002,
message: 'id is required and must be a string, when creating a UniversalStore',
});
}
}

export class UniversalStoreNotReadyError extends UniversalStoreError {
constructor(public data: { id: string; action: 'set state' | 'send event' }) {
super({
name: 'UniversalStoreNotReadyError',
code: 1003,
message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.`,
});
Comment on lines +42 to +48

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 | ⚡ Quick win

store.readyPromise in the error text is outdated; use untilReady().

The message currently points to a non-existent/publicly incorrect readiness API. UniversalStore exposes untilReady().

Proposed patch
 export class UniversalStoreNotReadyError extends UniversalStoreError {
   constructor(public data: { id: string; action: 'set state' | 'send event' }) {
     super({
       name: 'UniversalStoreNotReadyError',
       code: 1003,
-      message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.`,
+      message: `Cannot ${data.action} before store with id '${data.id}' is ready. You can get the current status with store.status, or await store.untilReady() to wait for the store to be ready before sending events.`,
     });
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code/core/src/shared/universal-store/errors.ts` around lines 42 - 48, The
error message in UniversalStoreNotReadyError (constructor of class
UniversalStoreNotReadyError extending UniversalStoreError) references the
outdated API store.readyPromise; update the message string to reference the
correct readiness API untilReady() instead (e.g., "await store.untilReady()") so
that the message guides users to use untilReady(); ensure you only change the
message text in the constructor while keeping name, code, and the rest of the
template intact.

}
}

export class UniversalStoreMissingSubscribeArgumentError extends UniversalStoreError {
constructor(public data: { id: string }) {
super({
name: 'UniversalStoreMissingSubscribeArgumentError',
code: 1004,
message: `Missing first subscribe argument, or second if first is the event type, when subscribing to a UniversalStore with id '${data.id}'`,
});
}
}
40 changes: 5 additions & 35 deletions code/core/src/shared/universal-store/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ describe('UniversalStore', () => {
leader: true,
})
).toThrowErrorMatchingInlineSnapshot(
`[TypeError: UniversalStore is not constructable - use UniversalStore.create() instead]`
`[SB_MANAGER_UNIVERSAL-STORE_1001 (UniversalStoreNotConstructableError): UniversalStore is not constructable - use UniversalStore.create() instead]`
);
});

it('should throw when id is not provided', () => {
// Arrange, Act, Assert - creating an instance without an id and expect it to throw
expect(() => (UniversalStore as any).create()).toThrowErrorMatchingInlineSnapshot(
`[TypeError: id is required and must be a string, when creating a UniversalStore]`
`[SB_MANAGER_UNIVERSAL-STORE_1002 (UniversalStoreIdRequiredError): id is required and must be a string, when creating a UniversalStore]`
);
});

Expand Down Expand Up @@ -691,7 +691,7 @@ You should reuse the existing instance instead of trying to create a new one.`);
// Assert - eventually the follower.untilReady() promise should throw an error when the timeout is reached
vi.advanceTimersToNextTimer();
await expect(follower.untilReady()).rejects.toThrowErrorMatchingInlineSnapshot(
`[TypeError: No existing state found for follower with id: 'env1:test'. Make sure a leader with the same id exists before creating a follower.]`
`[SB_MANAGER_UNIVERSAL-STORE_0001 (UniversalStoreFollowerTimeoutError): No existing state found for follower with id: 'env1:test'. Make sure a leader with the same id exists before creating a follower.]`
);
expect(follower.status).toBe(UniversalStore.Status.ERROR);
});
Expand Down Expand Up @@ -942,22 +942,7 @@ You should reuse the existing instance instead of trying to create a new one.`);
expect(follower.status).toBe(UniversalStore.Status.SYNCING);

// Act & Assert - set state on the follower before it is ready and expect it to throw
expect(() => follower.setState({ count: 1 })).toThrowErrorMatchingInlineSnapshot(`
[TypeError: Cannot set state before store is ready. You can get the current status with store.status,
or await store.readyPromise to wait for the store to be ready before sending events.
{
"newState": {
"count": 1
},
"id": "env2:test",
"actor": {
"id": "m7405c0077777777778",
"type": "FOLLOWER",
"environment": "MANAGER"
},
"environment": "MANAGER"
}]
`);
expect(() => follower.setState({ count: 1 })).toThrowErrorMatchingInlineSnapshot(`[SB_MANAGER_UNIVERSAL-STORE_1003 (UniversalStoreNotReadyError): Cannot set state before store with id 'env2:test' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.]`);
});
});

Expand Down Expand Up @@ -1127,22 +1112,7 @@ You should reuse the existing instance instead of trying to create a new one.`);
expect(follower.status).toBe(UniversalStore.Status.SYNCING);

// Act & Assert - send an event with the follower before it is ready and expect it to throw
expect(() => follower.send({ type: 'TOO_EARLY' })).toThrowErrorMatchingInlineSnapshot(`
[TypeError: Cannot send event before store is ready. You can get the current status with store.status,
or await store.readyPromise to wait for the store to be ready before sending events.
{
"event": {
"type": "TOO_EARLY"
},
"id": "env2:test",
"actor": {
"id": "m7405c0077777777778",
"type": "FOLLOWER",
"environment": "MANAGER"
},
"environment": "MANAGER"
}]
`);
expect(() => follower.send({ type: 'TOO_EARLY' })).toThrowErrorMatchingInlineSnapshot(`[SB_MANAGER_UNIVERSAL-STORE_1003 (UniversalStoreNotReadyError): Cannot send event before store with id 'env2:test' is ready. You can get the current status with store.status, or await store.readyPromise to wait for the store to be ready before sending events.]`);
});
});

Expand Down
55 changes: 14 additions & 41 deletions code/core/src/shared/universal-store/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { dedent } from 'ts-dedent';

import { instances } from './instances';
import {
UniversalStoreFollowerTimeoutError,
UniversalStoreIdRequiredError,
UniversalStoreMissingSubscribeArgumentError,
UniversalStoreNotConstructableError,
UniversalStoreNotReadyError,
} from './errors';
import type {
Actor,
ChannelEvent,
Expand Down Expand Up @@ -251,9 +258,7 @@ export class UniversalStore<
// it can only be called from within the static factory method create()
// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_propertiessimulating_private_constructors
if (!UniversalStore.isInternalConstructing) {
throw new TypeError(
'UniversalStore is not constructable - use UniversalStore.create() instead'
);
throw new UniversalStoreNotConstructableError();
}
UniversalStore.isInternalConstructing = false;

Expand Down Expand Up @@ -336,7 +341,7 @@ export class UniversalStore<
CustomEvent extends { type: string; payload?: any } = { type: string; payload?: any },
>(options: StoreOptions<State>): UniversalStore<State, CustomEvent> {
if (!options || typeof options?.id !== 'string') {
throw new TypeError('id is required and must be a string, when creating a UniversalStore');
throw new UniversalStoreIdRequiredError();
}
if (options.debug) {
console.debug(
Expand Down Expand Up @@ -390,24 +395,11 @@ export class UniversalStore<
this.debug('setState', { newState, previousState, updater });

if (this.status !== UniversalStore.Status.READY) {
throw new TypeError(
dedent`Cannot set state before store is ready. You can get the current status with store.status,
or await store.readyPromise to wait for the store to be ready before sending events.
${JSON.stringify(
{
newState,
id: this.id,
actor: this.actor,
environment: this.environment,
},
null,
2
)}`
);
throw new UniversalStoreNotReadyError({ id: this.id, action: 'set state' });
}

this.state = newState;
const event = {
const event: SetStateEvent<State> = {
type: UniversalStore.InternalEventType.SET_STATE,
payload: {
state: newState,
Expand Down Expand Up @@ -442,9 +434,7 @@ export class UniversalStore<
this.debug('subscribe', { eventType, listener });

if (!listener) {
throw new TypeError(
`Missing first subscribe argument, or second if first is the event type, when subscribing to a UniversalStore with id '${this.id}'`
);
throw new UniversalStoreMissingSubscribeArgumentError({ id: this.id });
}

if (!this.listeners.has(eventType)) {
Expand Down Expand Up @@ -485,20 +475,7 @@ export class UniversalStore<
public send = (event: CustomEvent) => {
this.debug('send', { event });
if (this.status !== UniversalStore.Status.READY) {
throw new TypeError(
dedent`Cannot send event before store is ready. You can get the current status with store.status,
or await store.readyPromise to wait for the store to be ready before sending events.
${JSON.stringify(
{
event,
id: this.id,
actor: this.actor,
environment: this.environment,
},
null,
2
)}`
);
throw new UniversalStoreNotReadyError({ id: this.id, action: 'send event' });
}
this.emitToListeners(event, { actor: this.actor });
this.emitToChannel(event, { actor: this.actor });
Expand Down Expand Up @@ -544,11 +521,7 @@ export class UniversalStore<
setTimeout(() => {
// if the state is already resolved by a response before this timeout,
// rejecting it doesn't do anything, it will be ignored
this.syncing!.reject!(
new TypeError(
`No existing state found for follower with id: '${this.id}'. Make sure a leader with the same id exists before creating a follower.`
)
);
this.syncing!.reject!(new UniversalStoreFollowerTimeoutError({ id: this.id }));
}, 1000);
}
}
Expand Down
2 changes: 1 addition & 1 deletion code/core/src/types/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ export interface StorybookConfigRaw {

build?: TestBuildConfig;

stories: StoriesEntry[];
stories: StoriesEntry[] | (() => Promise<StoriesEntry[]>);

framework?: Preset;

Expand Down
7 changes: 3 additions & 4 deletions code/frameworks/nextjs-vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { ReactPreview } from '@storybook/react';
import { __definePreview } from '@storybook/react';
import type { ReactTypes } from '@storybook/react';

import type vitePluginStorybookNextJs from 'vite-plugin-storybook-nextjs';
import type { storybookNextJsPlugin } from './vite-plugin';

import * as nextPreview from './preview';
import type { NextJsTypes } from './types';
Expand All @@ -17,9 +17,8 @@ export * from './types';

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
declare module '@storybook/nextjs-vite/vite-plugin' {
export const storybookNextJsPlugin: typeof vitePluginStorybookNextJs;
}

export type StorybookNextJsPlugin = typeof storybookNextJsPlugin;

export function definePreview<Addons extends PreviewAddon<never>[]>(
preview: { addons?: Addons } & ProjectAnnotations<ReactTypes & NextJsTypes & InferTypes<Addons>>
Expand Down
10 changes: 7 additions & 3 deletions code/lib/cli-storybook/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,10 +803,14 @@ export const getStoriesPathsFromConfig = async ({
return [];
}

const normalizedStories = normalizeStories(stories, {
configDir,
const resolvedStories = typeof stories === 'function'
? await stories()
: stories;

const normalizedStories = normalizeStories(resolvedStories, {
configDir,
workingDir,
});
});

const matchingStoryFiles = await StoryIndexGenerator.findMatchingFilesForSpecifiers(
normalizedStories,
Expand Down
Loading
Loading