From d6dae522e55bf6229d1f36124afc48890c507752 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Thu, 29 Jan 2026 14:39:35 +0100 Subject: [PATCH 01/11] Update Storybook dependencies and add ShareMenu component - Updated Storybook-related dependencies in package.json and yarn.lock to specific versions. - Introduced new ShareMenu component for managing build events and displaying build progress. - Added SHARE_PROVIDER_ID constant and integrated it into the manager. - Implemented publish-only build functionality in runChromaticBuild and related utilities. - Enhanced constants and types to support new features, including isPublishOnly flag. - Added disableSnapshots.ts to manage snapshot behavior during builds. --- package.json | 13 +- src/ShareMenu.stories.tsx | 25 +++ src/ShareMenu.tsx | 232 ++++++++++++++++++++++ src/constants.ts | 1 + src/disableSnapshots.ts | 9 + src/manager.tsx | 14 +- src/preset.ts | 29 ++- src/runChromaticBuild.ts | 43 +++- src/types.ts | 3 + src/utils/useBuildEvents.ts | 6 +- yarn.lock | 386 +++++++++++++++++++++++++++++------- 11 files changed, 665 insertions(+), 96 deletions(-) create mode 100644 src/ShareMenu.stories.tsx create mode 100644 src/ShareMenu.tsx create mode 100644 src/disableSnapshots.ts diff --git a/package.json b/package.json index 2469ccaa..12deebdd 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "@graphql-typed-document-node/core": "^3.2.0", "@parcel/watcher": "^2.4.1", "@storybook/addon-designs": "^11.0.3", - "@storybook/addon-docs": "^10.1.0", + "@storybook/addon-docs": "0.0.0-pr-33653-sha-709242df", "@storybook/icons": "^2.0.1", - "@storybook/react-vite": "^10.1.0", + "@storybook/react-vite": "0.0.0-pr-33653-sha-709242df", "@types/jsonfile": "^6.1.1", "@types/node": "^22.13.5", "@types/pluralize": "^0.0.29", @@ -91,7 +91,7 @@ "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-storybook": "^10.1.0", + "eslint-plugin-storybook": "0.0.0-pr-33653-sha-709242df", "graphql": "^16.8.1", "msw": "^2.0.0", "msw-storybook-addon": "^2.0.6", @@ -105,7 +105,7 @@ "react-dom": "^18.3.1", "react-joyride": "^2.7.2", "rimraf": "^3.0.2", - "storybook": "^10.1.0", + "storybook": "0.0.0-pr-33653-sha-709242df", "ts-dedent": "^2.2.0", "tsup": "^6.6.3", "typescript": "^5.7.3", @@ -119,7 +119,7 @@ "zx": "^1.14.1" }, "peerDependencies": { - "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" + "storybook": "0.0.0-pr-33653-sha-709242df" }, "packageManager": "yarn@4.10.3", "engines": { @@ -146,7 +146,8 @@ }, "bundler": { "exportEntries": [ - "./src/index.ts" + "./src/index.ts", + "./src/disableSnapshots.ts" ], "managerEntries": [ "./src/manager.tsx" diff --git a/src/ShareMenu.stories.tsx b/src/ShareMenu.stories.tsx new file mode 100644 index 00000000..f8bcdfec --- /dev/null +++ b/src/ShareMenu.stories.tsx @@ -0,0 +1,25 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn } from 'storybook/test'; + +import { ShareMenu } from './ShareMenu'; + +const meta = { + component: ShareMenu, + args: { + getAddonState: fn(), + setAddonState: fn(), + on: fn(), + off: fn(), + }, + argTypes: { + getAddonState: { type: 'function', target: 'manager-api' }, + setAddonState: { type: 'function', target: 'manager-api' }, + on: { type: 'function', target: 'manager-api' }, + off: { type: 'function', target: 'manager-api' }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/ShareMenu.tsx b/src/ShareMenu.tsx new file mode 100644 index 00000000..ca2a376c --- /dev/null +++ b/src/ShareMenu.tsx @@ -0,0 +1,232 @@ +import { FailedIcon } from '@storybook/icons'; +import { PlayHollowIcon, StopAltIcon } from '@storybook/icons'; +import pluralize from 'pluralize'; +import React, { useCallback, useContext, useEffect, useRef } from 'react'; +import { Link } from 'storybook/internal/components'; +import { Button, ProgressSpinner } from 'storybook/internal/components'; +import { + experimental_getTestProviderStore, + experimental_useStatusStore, + experimental_useTestProviderStore, + useStorybookApi, + useStorybookState, +} from 'storybook/manager-api'; +import { color, styled } from 'storybook/theming'; + +import { BUILD_STEP_CONFIG } from './buildSteps'; +import { BuildProgressInline } from './components/BuildProgressBarInline'; +import { + ADDON_ID, + CONFIG_INFO, + GIT_INFO_ERROR, + IS_OFFLINE, + IS_OUTDATED, + LOCAL_BUILD_PROGRESS, + PANEL_ID, + TEST_PROVIDER_ID, +} from './constants'; +import { ConfigInfoPayload, LocalBuildProgress } from './types'; +import { useAccessToken } from './utils/graphQLClient'; +import { TelemetryContext } from './utils/TelemetryContext'; +import { useBuildEvents } from './utils/useBuildEvents'; +import { useProjectId } from './utils/useProjectId'; +import { useSharedState } from './utils/useSharedState'; + +const Container = styled.div({ + display: 'flex', + justifyContent: 'space-between', + padding: '8px 0', +}); + +const Info = styled.div({ + display: 'flex', + flexDirection: 'column', + marginLeft: 8, +}); + +const Actions = styled.div({ + display: 'flex', + gap: 4, +}); + +const TitleWrapper = styled.div<{ crashed?: boolean }>(({ crashed, theme }) => ({ + fontSize: theme.typography.size.s1, + fontWeight: crashed ? 'bold' : 'normal', + color: crashed ? theme.color.negativeText : theme.color.defaultText, +})); + +const DescriptionWrapper = styled.div(({ theme }) => ({ + fontSize: theme.typography.size.s1, + color: theme.textMutedColor, +})); + +const Progress = styled(ProgressSpinner)({ + margin: 4, +}); + +const StopIcon = styled(StopAltIcon)({ + width: 10, +}); + +export const ShareMenu = () => { + const { addNotification } = useStorybookApi(); + + const trackEvent = useContext(TelemetryContext); + const { projectId } = useProjectId(); + const [accessToken] = useAccessToken(); + const isLoggedIn = !!accessToken; + + const [isOffline, setOffline] = useSharedState(IS_OFFLINE); + const [localBuildProgress] = useSharedState(LOCAL_BUILD_PROGRESS); + + const [configInfo] = useSharedState(CONFIG_INFO); + const hasConfigProblem = Object.keys(configInfo?.problems || {}).length > 0; + + const [gitInfoError] = useSharedState(GIT_INFO_ERROR); + + const lastStep = useRef(localBuildProgress?.currentStep); + + const testProviderState = experimental_useTestProviderStore( + (state) => state[TEST_PROVIDER_ID] ?? 'test-provider-state:pending' + ); + + const { startBuild, stopBuild } = useBuildEvents({ + localBuildProgress, + accessToken, + }); + + let warning: string | undefined; + if (isOffline) warning = 'Not available offline'; + if (hasConfigProblem) warning = 'Configuration problem'; + if (gitInfoError) warning = 'Git synchronization problem'; + if (!isLoggedIn) warning = 'Login required'; + if (!projectId) warning = 'Set up visual tests'; + + const isRunnable = !warning && testProviderState !== 'test-provider-state:crashed'; + + const startBuildIfPossible = useCallback(() => { + if (isRunnable) { + startBuild(true); + } + }, [isRunnable, startBuild]); + + useEffect( + () => experimental_getTestProviderStore(TEST_PROVIDER_ID).onRunAll(startBuildIfPossible), + [startBuildIfPossible] + ); + + const clickNotification = useCallback(({ onDismiss }: { onDismiss: () => void }) => { + onDismiss(); + }, []); + + useEffect(() => { + const offline = () => setOffline(true); + const online = () => setOffline(false); + window.addEventListener('offline', offline); + window.addEventListener('online', online); + return () => { + window.removeEventListener('offline', offline); + window.removeEventListener('online', online); + }; + }, [setOffline]); + + useEffect(() => { + if (localBuildProgress?.currentStep === lastStep.current) return; + lastStep.current = localBuildProgress?.currentStep; + + if (localBuildProgress?.currentStep === 'error') { + addNotification({ + id: `${ADDON_ID}/build-error/${Date.now()}`, + content: { + headline: 'Build error', + subHeadline: 'Check the Storybook process on the command line for more details.', + }, + icon: , + onClick: clickNotification, + }); + } + + if (localBuildProgress?.currentStep === 'limited') { + addNotification({ + id: `${ADDON_ID}/build-limited/${Date.now()}`, + content: { + headline: 'Build limited', + subHeadline: + 'Your account has insufficient snapshots remaining to run this build. Visit your billing page to find out more.', + }, + icon: , + onClick: clickNotification, + }); + } + }, [addNotification, clickNotification, localBuildProgress?.currentStep]); + + const clickWarning = useCallback(() => {}, []); + + let description: string | React.ReactNode; + switch (true) { + case !!warning: + description = {warning}; + break; + case testProviderState === 'test-provider-state:running': + description = localBuildProgress + ? BUILD_STEP_CONFIG[localBuildProgress.currentStep].renderProgress(localBuildProgress) + : 'Starting...'; + break; + case localBuildProgress?.currentStep === 'aborted': + description = 'Aborted by user'; + break; + case localBuildProgress?.currentStep === 'complete': + description = localBuildProgress.errorCount + ? `Encountered ${pluralize('component error', localBuildProgress.errorCount, true)}` + : `Published`; + break; + default: + description = 'Not run'; + } + + if (localBuildProgress?.isPublishOnly === false) { + description = 'Visual tests in progress'; + } + + return ( + + + {localBuildProgress?.isPublishOnly ? ( + + ) : ( + {description} + )} + + + + {warning ? null : testProviderState === 'test-provider-state:running' ? ( + + ) : ( + + )} + + + ); +}; diff --git a/src/constants.ts b/src/constants.ts index 6cbbf08b..df67a728 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,6 +8,7 @@ export const PACKAGE_NAME = '@chromatic-com/storybook'; export const ADDON_ID = 'chromaui/addon-visual-tests'; export const PANEL_ID = `${ADDON_ID}/panel`; +export const SHARE_PROVIDER_ID = `${ADDON_ID}/share-provider`; export const TEST_PROVIDER_ID = `${ADDON_ID}/test-provider`; export const ACCESS_TOKEN_KEY = `${ADDON_ID}/access-token/${CHROMATIC_BASE_URL}`; export const DEV_BUILD_ID_KEY = `${ADDON_ID}/dev-build-id`; diff --git a/src/disableSnapshots.ts b/src/disableSnapshots.ts new file mode 100644 index 00000000..6617e4e9 --- /dev/null +++ b/src/disableSnapshots.ts @@ -0,0 +1,9 @@ +import { definePreviewAddon } from 'storybook/internal/csf'; + +export default definePreviewAddon({ + parameters: { + chromatic: { + disableSnapshot: true, + }, + }, +}); diff --git a/src/manager.tsx b/src/manager.tsx index 8c593b5e..274901a5 100644 --- a/src/manager.tsx +++ b/src/manager.tsx @@ -1,9 +1,14 @@ import React from 'react'; -import { type Addon_TestProviderType, Addon_TypesEnum } from 'storybook/internal/types'; +import { + type Addon_ShareProviderType, + type Addon_TestProviderType, + Addon_TypesEnum, +} from 'storybook/internal/types'; import { addons, experimental_getStatusStore } from 'storybook/manager-api'; -import { ADDON_ID, PANEL_ID, PARAM_KEY, TEST_PROVIDER_ID } from './constants.ts'; +import { ADDON_ID, PANEL_ID, PARAM_KEY, SHARE_PROVIDER_ID, TEST_PROVIDER_ID } from './constants.ts'; import { Panel } from './Panel'; +import { ShareMenu } from './ShareMenu.tsx'; import { TestProviderRender } from './TestProviderRender'; addons.register(ADDON_ID, (api) => { @@ -26,6 +31,11 @@ addons.register(ADDON_ID, (api) => { api.togglePanel(true); }); + addons.add(SHARE_PROVIDER_ID, { + type: Addon_TypesEnum.experimental_SHARE_PROVIDER, + shareMenu: () => , + } satisfies Omit); + addons.add(TEST_PROVIDER_ID, { type: Addon_TypesEnum.experimental_TEST_PROVIDER, render: () => , diff --git a/src/preset.ts b/src/preset.ts index 5db3fa16..4960eb1f 100644 --- a/src/preset.ts +++ b/src/preset.ts @@ -13,7 +13,7 @@ import { import type { Channel } from 'storybook/internal/channels'; import { experimental_getTestProviderStore } from 'storybook/internal/core-server'; import { telemetry } from 'storybook/internal/telemetry'; -import type { Options } from 'storybook/internal/types'; +import type { Options, StorybookConfig } from 'storybook/internal/types'; import { ADDON_ID, @@ -229,11 +229,12 @@ async function serverChannel(channel: Channel, options: Options & { configFile?: channel ); - channel.on(START_BUILD, async ({ accessToken: userToken }) => { + channel.on(START_BUILD, async ({ accessToken: userToken, isPublishOnly = false }) => { const { projectId } = projectInfoState.value || {}; testProviderStore.runWithState(async () => { try { - await runChromaticBuild(localBuildProgress, { configFile, projectId, userToken }); + const chromaticOptions = { configFile, projectId, userToken }; + await runChromaticBuild(localBuildProgress, chromaticOptions, options, isPublishOnly); } catch (e) { console.error(`Failed to run Chromatic build, with error:\n${e}`); throw e; @@ -280,19 +281,23 @@ async function serverChannel(channel: Channel, options: Options & { configFile?: } const config = { + previewAnnotations: async (input = []) => { + const disableSnapshotsPreset = join( + dirname(require.resolve('@chromatic-com/storybook/package.json')), + 'dist/disableSnapshots.mjs' + ); + return process.env.SB_PUBLISH_ONLY === 'true' ? [...input, disableSnapshotsPreset] : input; + }, managerEntries, experimental_serverChannel: serverChannel, - staticDirs: async (inputDirs: string[]) => [ - ...inputDirs, + staticDirs: async (inputDirs) => [ + ...(inputDirs || []), { from: join(dirname(require.resolve('@chromatic-com/storybook/package.json')), 'assets'), to: 'addon-visual-tests-assets', }, ], - env: async ( - env: Record, - { configType }: { configType: 'DEVELOPMENT' | 'PRODUCTION' } - ) => { + env: async (env, { configType }) => { if (configType === 'PRODUCTION') return env; return { @@ -300,6 +305,12 @@ const config = { CHROMATIC_BASE_URL, }; }, +} satisfies Partial & { + managerEntries: (entry: string[]) => string[]; + experimental_serverChannel: ( + channel: Channel, + options: Options & { configFile?: string } + ) => Promise; }; export default config; diff --git a/src/runChromaticBuild.ts b/src/runChromaticBuild.ts index 48e06b22..175c4cb9 100644 --- a/src/runChromaticBuild.ts +++ b/src/runChromaticBuild.ts @@ -1,4 +1,14 @@ -import { Context, InitialContext, Options, run, TaskName } from 'chromatic/node'; +import { join } from 'node:path'; + +import { + Context, + InitialContext, + Options as ChromaticOptions, + run, + TaskName, +} from 'chromatic/node'; +import { buildStaticStandalone } from 'storybook/internal/core-server'; +import type { Options as StorybookOptions } from 'storybook/internal/types'; import { BUILD_STEP_CONFIG, @@ -58,7 +68,7 @@ export const onStartOrProgress = throw new Error('Unexpected missing value for localBuildProgress'); } - const { buildProgressPercentage, stepProgress, previousBuildProgress } = + const { buildProgressPercentage, stepProgress, previousBuildProgress, isPublishOnly } = localBuildProgress.value; // Ignore progress events for steps that have already completed @@ -106,6 +116,7 @@ export const onStartOrProgress = localBuildProgress.value = { buildId: ctx.announcedBuild?.id, branch: ctx.git?.branch, + isPublishOnly, buildProgressPercentage: Math.min(newPercentage, endPercentage), currentStep: ctx.task, stepProgress, @@ -128,10 +139,11 @@ export const onCompleteOrError = throw new Error('Unexpected missing value for localBuildProgress'); } - const { buildProgressPercentage, stepProgress } = localBuildProgress.value; + const { buildProgressPercentage, stepProgress, isPublishOnly } = localBuildProgress.value; const update = { buildId: ctx.announcedBuild?.id, branch: ctx.git?.branch, + isPublishOnly, buildProgressPercentage, stepProgress, previousBuildProgress: stepProgress, @@ -177,13 +189,16 @@ export const onCompleteOrError = export const runChromaticBuild = async ( localBuildProgress: ReturnType>, - options: Partial + chromaticOptions: Partial, + storybookOptions: StorybookOptions, + publishOnly: boolean ) => { - if (!options.projectId) throw new Error('Missing projectId'); - if (!options.userToken) throw new Error('Missing userToken'); + if (!chromaticOptions.projectId) throw new Error('Missing projectId'); + if (!chromaticOptions.userToken) throw new Error('Missing userToken'); // Set initial progress state. JSON.parse avoids mutating the constant. - localBuildProgress.value = JSON.parse(INITIAL_BUILD_PAYLOAD_JSON); + localBuildProgress.value = JSON.parse(INITIAL_BUILD_PAYLOAD_JSON) as LocalBuildProgress; + localBuildProgress.value.isPublishOnly = chromaticOptions.isPublishOnly; // Timeout is defined here so it's shared between all handlers let timeout: ReturnType | undefined; @@ -193,13 +208,25 @@ export const runChromaticBuild = async ( process.env.SB_TESTBUILD = 'true'; + const publishOnlyConfig: Partial = {}; + + if (publishOnly) { + process.env.SB_PUBLISH_ONLY = 'true'; + publishOnlyConfig.storybookBuildDir = join(process.cwd(), 'storybook-build-standalone'); + await buildStaticStandalone({ + ...storybookOptions, + outputDir: publishOnlyConfig.storybookBuildDir, + }); + } + await run({ flags: { interactive: false, }, options: { - ...options, + ...chromaticOptions, ...CONFIG_OVERRIDES, + ...publishOnlyConfig, experimental_onTaskStart: onStartOrProgress(localBuildProgress, timeout), experimental_onTaskProgress: onStartOrProgress(localBuildProgress, timeout), experimental_onTaskComplete: onCompleteOrError(localBuildProgress, timeout), diff --git a/src/types.ts b/src/types.ts index 4b7da287..ef2d83f2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,9 @@ export type LocalBuildProgress = { */ branch?: string; + /** Whether the build is a publish-only build (i.e. no visual tests) */ + isPublishOnly?: boolean; + /** Overall percentage of build progress */ buildProgressPercentage: number; diff --git a/src/utils/useBuildEvents.ts b/src/utils/useBuildEvents.ts index de9afadd..b49d9c10 100644 --- a/src/utils/useBuildEvents.ts +++ b/src/utils/useBuildEvents.ts @@ -30,11 +30,11 @@ export const useBuildEvents = ({ () => debounce( 'startBuild', - () => { + (isPublishOnly = false) => { setDisallowed(false); setStarting(true); - emit(START_BUILD, { accessToken }); - trackEvent?.({ action: 'startBuild' }); + emit(START_BUILD, { accessToken, isPublishOnly }); + trackEvent?.({ action: 'startBuild', isPublishOnly }); }, 1000, false diff --git a/yarn.lock b/yarn.lock index 77123629..8217133f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -452,9 +452,9 @@ __metadata: "@neoconfetti/react": "npm:^1.0.0" "@parcel/watcher": "npm:^2.4.1" "@storybook/addon-designs": "npm:^11.0.3" - "@storybook/addon-docs": "npm:^10.1.0" + "@storybook/addon-docs": "npm:0.0.0-pr-33653-sha-709242df" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-vite": "npm:^10.1.0" + "@storybook/react-vite": "npm:0.0.0-pr-33653-sha-709242df" "@types/jsonfile": "npm:^6.1.1" "@types/node": "npm:^22.13.5" "@types/pluralize": "npm:^0.0.29" @@ -476,7 +476,7 @@ __metadata: eslint-plugin-prettier: "npm:^5.2.3" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" - eslint-plugin-storybook: "npm:^10.1.0" + eslint-plugin-storybook: "npm:0.0.0-pr-33653-sha-709242df" filesize: "npm:^10.0.12" graphql: "npm:^16.8.1" jsonfile: "npm:^6.1.0" @@ -492,7 +492,7 @@ __metadata: react-dom: "npm:^18.3.1" react-joyride: "npm:^2.7.2" rimraf: "npm:^3.0.2" - storybook: "npm:^10.1.0" + storybook: "npm:0.0.0-pr-33653-sha-709242df" strip-ansi: "npm:^7.1.0" ts-dedent: "npm:^2.2.0" tsup: "npm:^6.6.3" @@ -506,7 +506,7 @@ __metadata: zod: "npm:^3.22.2" zx: "npm:^1.14.1" peerDependencies: - storybook: ^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 + storybook: 0.0.0-pr-33653-sha-709242df languageName: unknown linkType: soft @@ -1101,6 +1101,17 @@ __metadata: languageName: node linkType: hard +"@eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 + languageName: node + linkType: hard + "@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": version: 4.12.2 resolution: "@eslint-community/regexpp@npm:4.12.2" @@ -1975,6 +1986,22 @@ __metadata: languageName: node linkType: hard +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + "@isaacs/fs-minipass@npm:^4.0.0": version: 4.0.1 resolution: "@isaacs/fs-minipass@npm:4.0.1" @@ -1991,12 +2018,11 @@ __metadata: languageName: node linkType: hard -"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1": - version: 0.6.1 - resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.1" +"@joshwooding/vite-plugin-react-docgen-typescript@npm:^0.6.3": + version: 0.6.3 + resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.6.3" dependencies: - glob: "npm:^10.0.0" - magic-string: "npm:^0.30.0" + glob: "npm:^11.1.0" react-docgen-typescript: "npm:^2.2.2" peerDependencies: typescript: ">= 4.3.x" @@ -2004,7 +2030,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/0bcc2adbb49158018102bd9d84cd8572c770daee3d46733157933ef0330953bd5b9e102c26f2338ee7dfb8f21a7bb937134d23f8a7935d5dc88525a253557467 + checksum: 10c0/e68d2884235b8290673c17a13bc303a088feba6ce0a275ab0778b50e90b967f5dffdcf71ed3197e9cdf07607594a9cb2a86e3ea6e4eb8962b50d61078107bac3 languageName: node linkType: hard @@ -2717,46 +2743,45 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-docs@npm:^10.1.0": - version: 10.1.0 - resolution: "@storybook/addon-docs@npm:10.1.0" +"@storybook/addon-docs@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/addon-docs@npm:0.0.0-pr-33653-sha-709242df" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:10.1.0" - "@storybook/icons": "npm:^2.0.0" - "@storybook/react-dom-shim": "npm:10.1.0" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-709242df" + "@storybook/icons": "npm:^2.0.1" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-709242df" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.1.0 - checksum: 10c0/3da5ed05f47263b1034963c5ed59966a63f26c1a50a571ed77c7315e9305e0ae9b181aac1cfbdfcee4aa48fe32c3e826bbf7a29610f943392a312e19b18c9e80 + storybook: ^0.0.0-pr-33653-sha-709242df + checksum: 10c0/e63f9afe489b974fa30da4622911f99e2854c035a3af1ba36e716655303cab57093e31e62ca04080bd80809291b36a4c58c51e2113c544811107e3b92db1e77a languageName: node linkType: hard -"@storybook/builder-vite@npm:10.1.0": - version: 10.1.0 - resolution: "@storybook/builder-vite@npm:10.1.0" +"@storybook/builder-vite@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/builder-vite@npm:0.0.0-pr-33653-sha-709242df" dependencies: - "@storybook/csf-plugin": "npm:10.1.0" - "@vitest/mocker": "npm:3.2.4" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-709242df" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.1.0 + storybook: ^0.0.0-pr-33653-sha-709242df vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/b565e2fcb05d7caeae24f0a7f95d496f4210222c098184c99d74fa1f2d34df18fced43e9e60bbab424d1d13741441f03a5cd3e6a92e21c8a5a7482b4af5acc3f + checksum: 10c0/dbcc7c45eb78b604621b03371403ab1b25c91c98e164ad5abbc397ee675a32441c9a3e7ee40cae66edef4b0091ba92c2575055ba0ff1876e2297fef7bd036613 languageName: node linkType: hard -"@storybook/csf-plugin@npm:10.1.0": - version: 10.1.0 - resolution: "@storybook/csf-plugin@npm:10.1.0" +"@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-709242df" dependencies: unplugin: "npm:^2.3.5" peerDependencies: esbuild: "*" rollup: "*" - storybook: ^10.1.0 + storybook: ^0.0.0-pr-33653-sha-709242df vite: "*" webpack: "*" peerDependenciesMeta: @@ -2768,7 +2793,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10c0/6da01fc2d5fcfd25e4c31096144bdc451423f8d358245b8e8cbb955669df9dbc501f7068453a63d67be5453f8e70b9773c954e1c3f9faa7bb27b18436dcaed3f + checksum: 10c0/0adc73472e54c066e64bc7b23c2bcf09eccb86f39de58540e8bbbf4cca71fe420696fa93fdce8a909c2162ec526e0a8a3e73778cf67d5c95835cf544285bbd2d languageName: node linkType: hard @@ -2779,7 +2804,7 @@ __metadata: languageName: node linkType: hard -"@storybook/icons@npm:^2.0.0, @storybook/icons@npm:^2.0.1": +"@storybook/icons@npm:^2.0.1": version: 2.0.1 resolution: "@storybook/icons@npm:2.0.1" peerDependencies: @@ -2789,25 +2814,25 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:10.1.0": - version: 10.1.0 - resolution: "@storybook/react-dom-shim@npm:10.1.0" +"@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-709242df" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.0 - checksum: 10c0/5545e858de3023426182a9a9e2bcd4dca2441366234826ea2cc70516eccd72a5188a572e03322517c84d54eff806c3be74ae1557ef8d85a013d4a68c20a05f18 + storybook: ^0.0.0-pr-33653-sha-709242df + checksum: 10c0/73ba8e85a9f51aebf22dbe11a932248dd94fe30c6e83abe019cabecb13c17009ee0d4a2f843c83fa832a8afc8632d51375bd1ba068e16806b84e0b496ef4ceb0 languageName: node linkType: hard -"@storybook/react-vite@npm:^10.1.0": - version: 10.1.0 - resolution: "@storybook/react-vite@npm:10.1.0" +"@storybook/react-vite@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/react-vite@npm:0.0.0-pr-33653-sha-709242df" dependencies: - "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.6.1" + "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:10.1.0" - "@storybook/react": "npm:10.1.0" + "@storybook/builder-vite": "npm:0.0.0-pr-33653-sha-709242df" + "@storybook/react": "npm:0.0.0-pr-33653-sha-709242df" empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -2816,28 +2841,28 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.0 + storybook: ^0.0.0-pr-33653-sha-709242df vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/d61b40520afd71881710d8c3b7a1590ba9fa67fee236c2dc58a52d1e31f33c08fdd6cfded28ca8c484aedfb4c583cf243cac8d99fec045f3a8de6f3652ee0eac + checksum: 10c0/097757779cad264eb1553310101fa4416c694fbe0468732d900e7569dd18c7fb4312b4838f55da48117e4a7f2519b9a6457d5fb43f125274e14139223ae2faad languageName: node linkType: hard -"@storybook/react@npm:10.1.0": - version: 10.1.0 - resolution: "@storybook/react@npm:10.1.0" +"@storybook/react@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "@storybook/react@npm:0.0.0-pr-33653-sha-709242df" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:10.1.0" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-709242df" react-docgen: "npm:^8.0.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.1.0 + storybook: ^0.0.0-pr-33653-sha-709242df typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/2d05b1768dcaa69b53d7ccf8cc1273527c3b776b79c0246783a6e9378c5b7376660fc99908d0c3c9ab4ebeed9a7f6ff6085dee3157593c401ac0e8a644235d95 + checksum: 10c0/08cfcfb23f4eb40437a838f5996909b2b184b1abbd8592115e4fd136d92b8c93b8aa266b62f78a09baa9d3199d3c3c91e2214338528763b405e9d3bfad504aaa languageName: node linkType: hard @@ -3177,6 +3202,19 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/project-service@npm:8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/project-service@npm:8.54.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.54.0" + "@typescript-eslint/types": "npm:^8.54.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/3392ae259199021a80616a44d9484d1c363f61bc5c631dff2d08c6a906c98716a20caa7b832b8970120a1eb1eb2de3ee890cd527d6edb04f532f4e48a690a792 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:8.46.2": version: 8.46.2 resolution: "@typescript-eslint/scope-manager@npm:8.46.2" @@ -3187,6 +3225,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/scope-manager@npm:8.54.0" + dependencies: + "@typescript-eslint/types": "npm:8.54.0" + "@typescript-eslint/visitor-keys": "npm:8.54.0" + checksum: 10c0/794740a5c0c1afc38d71e6bc59cc62870286e40d99f15e9760e76fb3d4197e961ee151c286c428535c404f5137721242a14da21350b749d0feb1f589f167814f + languageName: node + linkType: hard + "@typescript-eslint/tsconfig-utils@npm:8.46.2, @typescript-eslint/tsconfig-utils@npm:^8.46.2": version: 8.46.2 resolution: "@typescript-eslint/tsconfig-utils@npm:8.46.2" @@ -3196,6 +3244,15 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/tsconfig-utils@npm:8.54.0, @typescript-eslint/tsconfig-utils@npm:^8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.54.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/e8598b0f051650c085d749002138d12249a3efd03e7de02e9e7913939dddd649d159b91f29ca3d28f5ee798b3f528a7195688e23c5e0b315d534e7af20a0c99a + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:8.46.2": version: 8.46.2 resolution: "@typescript-eslint/type-utils@npm:8.46.2" @@ -3219,6 +3276,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:8.54.0, @typescript-eslint/types@npm:^8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/types@npm:8.54.0" + checksum: 10c0/2219594fe5e8931ff91fd1b7a2606d33cd4f093d43f9ca71bcaa37f106ef79ad51f830dea51392f7e3d8bca77f7077ef98733f87bc008fad2f0bbd9ea5fb8a40 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:8.46.2": version: 8.46.2 resolution: "@typescript-eslint/typescript-estree@npm:8.46.2" @@ -3239,7 +3303,26 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.46.2, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0, @typescript-eslint/utils@npm:^8.8.1": +"@typescript-eslint/typescript-estree@npm:8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.54.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.54.0" + "@typescript-eslint/tsconfig-utils": "npm:8.54.0" + "@typescript-eslint/types": "npm:8.54.0" + "@typescript-eslint/visitor-keys": "npm:8.54.0" + debug: "npm:^4.4.3" + minimatch: "npm:^9.0.5" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.4.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/1a1a7c0a318e71f3547ab5573198d36165ea152c50447ef92e6326303f9a5c397606201ba80c7b86a725dcdd2913e924be94466a0c33b1b0c3ee852059e646b6 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.46.2, @typescript-eslint/utils@npm:^6.0.0 || ^7.0.0 || ^8.0.0": version: 8.46.2 resolution: "@typescript-eslint/utils@npm:8.46.2" dependencies: @@ -3254,6 +3337,21 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^8.48.0": + version: 8.54.0 + resolution: "@typescript-eslint/utils@npm:8.54.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.54.0" + "@typescript-eslint/types": "npm:8.54.0" + "@typescript-eslint/typescript-estree": "npm:8.54.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/949a97dca8024d39666e04ecdf2d4e12722f5064c387901e72bdcc7adafb96cf650a070dc79f9dd46fa1aae6ac2b5eac5ae3fe5a6979385208c28809a1bd143f + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:8.46.2": version: 8.46.2 resolution: "@typescript-eslint/visitor-keys@npm:8.46.2" @@ -3264,6 +3362,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:8.54.0": + version: 8.54.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.54.0" + dependencies: + "@typescript-eslint/types": "npm:8.54.0" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/f83a9aa92f7f4d1fdb12cbca28c6f5704c36371264606b456388b2c869fc61e73c86d3736556e1bb6e253f3a607128b5b1bf6c68395800ca06f18705576faadd + languageName: node + linkType: hard + "@urql/core@npm:^5.1.1": version: 5.2.0 resolution: "@urql/core@npm:5.2.0" @@ -3936,6 +4044,15 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10c0/8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + "bundle-require@npm:^4.0.0": version: 4.2.1 resolution: "bundle-require@npm:4.2.1" @@ -4541,7 +4658,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.4.3, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -4595,6 +4712,23 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^5.0.0": + version: 5.0.1 + resolution: "default-browser-id@npm:5.0.1" + checksum: 10c0/5288b3094c740ef3a86df9b999b04ff5ba4dee6b64e7b355c0fff5217752c8c86908d67f32f6cba9bb4f9b7b61a1b640c0a4f9e34c57e0ff3493559a625245ee + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.4.0 + resolution: "default-browser@npm:5.4.0" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10c0/a49ddd0c7b1a319163f64a5fc68ebb45a98548ea23a3155e04518f026173d85cfa2f451b646366c36c8f70b01e4cb773e23d1d22d2c61d8b84e5fbf151b4b609 + languageName: node + linkType: hard + "defaults@npm:^1.0.3": version: 1.0.4 resolution: "defaults@npm:1.0.4" @@ -4615,6 +4749,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10c0/5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + "define-properties@npm:^1.2.1": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -5289,15 +5430,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^10.1.0": - version: 10.1.0 - resolution: "eslint-plugin-storybook@npm:10.1.0" +"eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-709242df" dependencies: - "@typescript-eslint/utils": "npm:^8.8.1" + "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^10.1.0 - checksum: 10c0/25fdfc21cde3f07c6d0a84690a434cb981808e6fdbd811b7c96b73b7aedaae7bbf1e9a20f009ed109803c408aa45de73d93049acc0e0e363a271a94f7074cf7b + storybook: ^0.0.0-pr-33653-sha-709242df + checksum: 10c0/061aaed2dc3ee468210bf20ed3d7e0e723fdd9ceadf1d80a3ee9fe13b7020ace2234b84d0c2dc1a6189fa2edb7f1188cc673d1c0b4914da40fc94c6e49c671af languageName: node linkType: hard @@ -5681,7 +5822,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0": +"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -5898,7 +6039,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -5914,6 +6055,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.1.0": + version: 11.1.0 + resolution: "glob@npm:11.1.0" + dependencies: + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/1ceae07f23e316a6fa74581d9a74be6e8c2e590d2f7205034dd5c0435c53f5f7b712c2be00c3b65bf0a49294a1c6f4b98cd84c7637e29453b5aa13b79f1763a2 + languageName: node + linkType: hard + "glob@npm:^7.1.2, glob@npm:^7.1.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -6501,6 +6658,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -6546,6 +6712,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -6753,6 +6930,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -7212,6 +7398,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.2.5 + resolution: "lru-cache@npm:11.2.5" + checksum: 10c0/cc98958d25dddf1c8a8cbdc49588bd3b24450e8dfa78f32168fd188a20d4a0331c7406d0f3250c86a46619ee288056fd7a1195e8df56dc8a9592397f4fbd8e1d + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -7370,6 +7563,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + "minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -7826,6 +8028,18 @@ __metadata: languageName: node linkType: hard +"open@npm:^10.2.0": + version: 10.2.0 + resolution: "open@npm:10.2.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + wsl-utils: "npm:^0.1.0" + checksum: 10c0/5a36d0c1fd2f74ce553beb427ca8b8494b623fc22c6132d0c1688f246a375e24584ea0b44c67133d9ab774fa69be8e12fbe1ff12504b1142bd960fb09671948f + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.4 resolution: "optionator@npm:0.9.4" @@ -8113,6 +8327,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + "path-to-regexp@npm:^6.3.0": version: 6.3.0 resolution: "path-to-regexp@npm:6.3.0" @@ -8859,6 +9083,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.1.0 + resolution: "run-applescript@npm:7.1.0" + checksum: 10c0/ab826c57c20f244b2ee807704b1ef4ba7f566aa766481ae5922aac785e2570809e297c69afcccc3593095b538a8a77d26f2b2e9a1d9dffee24e0e039502d1a03 + languageName: node + linkType: hard + "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -8987,7 +9218,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.2": +"semver@npm:^7.0.0, semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.7.3": version: 7.7.3 resolution: "semver@npm:7.7.3" bin: @@ -9374,19 +9605,20 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^10.1.0": - version: 10.1.0 - resolution: "storybook@npm:10.1.0" +"storybook@npm:0.0.0-pr-33653-sha-709242df": + version: 0.0.0-pr-33653-sha-709242df + resolution: "storybook@npm:0.0.0-pr-33653-sha-709242df" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/icons": "npm:^2.0.0" + "@storybook/icons": "npm:^2.0.1" "@testing-library/jest-dom": "npm:^6.6.3" "@testing-library/user-event": "npm:^14.6.1" "@vitest/expect": "npm:3.2.4" "@vitest/spy": "npm:3.2.4" esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0" + open: "npm:^10.2.0" recast: "npm:^0.23.5" - semver: "npm:^7.6.2" + semver: "npm:^7.7.3" use-sync-external-store: "npm:^1.5.0" ws: "npm:^8.18.0" peerDependencies: @@ -9396,7 +9628,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10c0/a8a73cf033898b6d2c59f936a2089c62ecc10b494cb4e0a7d459e27bf9f45c60971f80a14370eda29155e31db84a0782820c6404af22641134c68a0e62e1ae38 + checksum: 10c0/e6323db3bcfd91c63e073974337dccdb8ed92b4285120ba10b0a7485c461cb2a6bf9daf8ec2d579012f48f47822c3ca3fa06447bf787bad24d0fe95c88afe7a4 languageName: node linkType: hard @@ -9880,6 +10112,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^2.4.0": + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 + languageName: node + linkType: hard + "ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0" @@ -10825,6 +11066,15 @@ __metadata: languageName: node linkType: hard +"wsl-utils@npm:^0.1.0": + version: 0.1.0 + resolution: "wsl-utils@npm:0.1.0" + dependencies: + is-wsl: "npm:^3.1.0" + checksum: 10c0/44318f3585eb97be994fc21a20ddab2649feaf1fbe893f1f866d936eea3d5f8c743bec6dc02e49fbdd3c0e69e9b36f449d90a0b165a4f47dd089747af4cf2377 + languageName: node + linkType: hard + "y18n@npm:^5.0.5": version: 5.0.8 resolution: "y18n@npm:5.0.8" From cd47e0e2c052e53034ebaded141d87b326f2cca3 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Thu, 29 Jan 2026 15:07:04 +0100 Subject: [PATCH 02/11] fix tsconfig.json --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index cb291335..5bcad7df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "noEmit": true, "allowImportingTsExtensions": true, "allowSyntheticDefaultImports": true, "moduleResolution": "bundler", From 1e07b621080d9af4cd00ce034dec804e601a1a74 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 09:52:04 +0100 Subject: [PATCH 03/11] refactor auth state, so it can be shared and isOpen represents the modal. --- src/AuthContext.tsx | 24 ------- src/Panel.tsx | 60 +++++++---------- src/ShareMenu.stories.tsx | 1 + src/ShareMenu.tsx | 17 +++-- src/TestProviderRender.tsx | 18 ++++-- src/components/FooterMenu.tsx | 10 +-- src/manager.tsx | 4 +- src/screens/Authentication/Authentication.tsx | 60 ++++++++--------- src/screens/Authentication/Verify.tsx | 15 ++--- src/screens/VisualTests/NoBuild.tsx | 14 ++-- src/utils/graphQLClient.tsx | 64 +++++++++++++------ 11 files changed, 144 insertions(+), 143 deletions(-) delete mode 100644 src/AuthContext.tsx diff --git a/src/AuthContext.tsx b/src/AuthContext.tsx deleted file mode 100644 index 3967f3f5..00000000 --- a/src/AuthContext.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { createContext } from 'react'; - -import { useRequiredContext } from './utils/useRequiredContext'; - -interface AuthState { - accessToken: string | null; - setAccessToken: (accessToken: string | null) => void; - subdomain: string; - setSubdomain: (subdomain: string) => void; -} - -export const AuthContext = createContext(null); - -export const AuthProvider = ({ - children, - value, -}: { - children: React.ReactNode; - value: AuthState; -}) => { - return {children}; -}; - -export const useAuthState = () => useRequiredContext(AuthContext, 'AuthState'); diff --git a/src/Panel.tsx b/src/Panel.tsx index 7818caad..984a76e7 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -1,8 +1,7 @@ import React, { useCallback } from 'react'; import type { API } from 'storybook/manager-api'; -import { experimental_getStatusStore, useChannel, useStorybookState } from 'storybook/manager-api'; +import { experimental_getStatusStore, useStorybookState } from 'storybook/manager-api'; -import { AuthProvider } from './AuthContext'; import { Spinner } from './components/design-system'; import { ADDON_ID, @@ -27,12 +26,12 @@ import { ControlsProvider } from './screens/VisualTests/ControlsContext'; import { RunBuildProvider } from './screens/VisualTests/RunBuildContext'; import { VisualTests } from './screens/VisualTests/VisualTests'; import { GitInfoPayload, LocalBuildProgress, UpdateStatusFunction } from './types'; -import { createClient, GraphQLClientProvider, useAccessToken } from './utils/graphQLClient'; +import { createClient, GraphQLClientProvider, useAuth } from './utils/graphQLClient'; import { TelemetryProvider } from './utils/TelemetryContext'; import { useBuildEvents } from './utils/useBuildEvents'; import { useChannelFetch } from './utils/useChannelFetch'; import { useProjectId } from './utils/useProjectId'; -import { clearSessionState, useSessionState } from './utils/useSessionState'; +import { useSessionState } from './utils/useSessionState'; import { useSharedState } from './utils/useSharedState'; interface PanelProps { @@ -42,15 +41,8 @@ interface PanelProps { const statusStore = experimental_getStatusStore(ADDON_ID); -export const Panel = ({ active }: PanelProps) => { - const [accessToken, updateAccessToken] = useAccessToken(); - const setAccessToken = useCallback( - (token: string | null) => { - updateAccessToken(token); - if (!token) clearSessionState('authenticationScreen', 'exchangeParameters'); - }, - [updateAccessToken] - ); +export const Panel = ({ active, api }: PanelProps) => { + const [auth] = useAuth(); const { storyId } = useStorybookState(); const [gitInfo] = useSharedState(GIT_INFO); @@ -60,7 +52,7 @@ export const Panel = ({ active }: PanelProps) => { const [localBuildProgress, setLocalBuildProgress] = useSharedState(LOCAL_BUILD_PROGRESS); const [, setOutdated] = useSharedState(IS_OUTDATED); - const emit = useChannel({}); + const { emit } = api; const updateBuildStatus = useCallback((statuses) => { statusStore.unset(); @@ -80,30 +72,30 @@ export const Panel = ({ active }: PanelProps) => { // If the user creates a project in a dialog (either during login or later, it get set here) const [createdProjectId, setCreatedProjectId] = useSessionState('createdProjectId'); const [addonUninstalled, setAddonUninstalled] = useSharedState(REMOVE_ADDON); - const [subdomain, setSubdomain] = useSessionState('subdomain', 'www'); const trackEvent = useCallback((data: any) => emit(TELEMETRY, data), [emit]); - const { isRunning, startBuild, stopBuild } = useBuildEvents({ localBuildProgress, accessToken }); + const { isRunning, startBuild, stopBuild } = useBuildEvents({ + localBuildProgress, + accessToken: auth.token, + }); const channelFetch = useChannelFetch(); const fetch = globalThis.LOGLEVEL === 'debug' ? globalThis.fetch : channelFetch; const withProviders = (children: React.ReactNode) => ( - - - - - - - - - + + + + + + + ); @@ -125,13 +117,9 @@ export const Panel = ({ active }: PanelProps) => { } // Render the Authentication flow if the user is not signed in. - if (!accessToken) { + if (!auth.token) { return withProviders( - + ); } diff --git a/src/ShareMenu.stories.tsx b/src/ShareMenu.stories.tsx index f8bcdfec..209827d2 100644 --- a/src/ShareMenu.stories.tsx +++ b/src/ShareMenu.stories.tsx @@ -6,6 +6,7 @@ import { ShareMenu } from './ShareMenu'; const meta = { component: ShareMenu, args: { + api: {}, getAddonState: fn(), setAddonState: fn(), on: fn(), diff --git a/src/ShareMenu.tsx b/src/ShareMenu.tsx index ca2a376c..d2d23b4a 100644 --- a/src/ShareMenu.tsx +++ b/src/ShareMenu.tsx @@ -5,6 +5,7 @@ import React, { useCallback, useContext, useEffect, useRef } from 'react'; import { Link } from 'storybook/internal/components'; import { Button, ProgressSpinner } from 'storybook/internal/components'; import { + API, experimental_getTestProviderStore, experimental_useStatusStore, experimental_useTestProviderStore, @@ -26,7 +27,7 @@ import { TEST_PROVIDER_ID, } from './constants'; import { ConfigInfoPayload, LocalBuildProgress } from './types'; -import { useAccessToken } from './utils/graphQLClient'; +import { useAuth } from './utils/graphQLClient'; import { TelemetryContext } from './utils/TelemetryContext'; import { useBuildEvents } from './utils/useBuildEvents'; import { useProjectId } from './utils/useProjectId'; @@ -68,13 +69,17 @@ const StopIcon = styled(StopAltIcon)({ width: 10, }); -export const ShareMenu = () => { - const { addNotification } = useStorybookApi(); +interface Props { + api: API; +} + +export const ShareMenu = ({ api }: Props) => { + const { addNotification } = api; const trackEvent = useContext(TelemetryContext); const { projectId } = useProjectId(); - const [accessToken] = useAccessToken(); - const isLoggedIn = !!accessToken; + const [auth] = useAuth(); + const isLoggedIn = !!auth.token; const [isOffline, setOffline] = useSharedState(IS_OFFLINE); const [localBuildProgress] = useSharedState(LOCAL_BUILD_PROGRESS); @@ -92,7 +97,7 @@ export const ShareMenu = () => { const { startBuild, stopBuild } = useBuildEvents({ localBuildProgress, - accessToken, + accessToken: auth.token, }); let warning: string | undefined; diff --git a/src/TestProviderRender.tsx b/src/TestProviderRender.tsx index fc2d7e87..a422cb2a 100644 --- a/src/TestProviderRender.tsx +++ b/src/TestProviderRender.tsx @@ -5,10 +5,10 @@ import React, { useCallback, useContext, useEffect, useRef } from 'react'; import { Link } from 'storybook/internal/components'; import { Button, ProgressSpinner } from 'storybook/internal/components'; import { + type API, experimental_getTestProviderStore, experimental_useStatusStore, experimental_useTestProviderStore, - useStorybookApi, useStorybookState, } from 'storybook/manager-api'; import { color, styled } from 'storybook/theming'; @@ -25,7 +25,7 @@ import { TEST_PROVIDER_ID, } from './constants'; import { ConfigInfoPayload, LocalBuildProgress } from './types'; -import { useAccessToken } from './utils/graphQLClient'; +import { useAuth } from './utils/graphQLClient'; import { TelemetryContext } from './utils/TelemetryContext'; import { useBuildEvents } from './utils/useBuildEvents'; import { useProjectId } from './utils/useProjectId'; @@ -67,8 +67,12 @@ const StopIcon = styled(StopAltIcon)({ width: 10, }); -export const TestProviderRender = () => { - const { addNotification, selectStory, setOptions, togglePanel } = useStorybookApi(); +interface Props { + api: API; +} + +export const TestProviderRender = ({ api }: Props) => { + const { addNotification, selectStory, setOptions, togglePanel } = api; const warningStatusCount = experimental_useStatusStore( (allStatuses) => Object.values(allStatuses) @@ -78,8 +82,8 @@ export const TestProviderRender = () => { const trackEvent = useContext(TelemetryContext); const { projectId } = useProjectId(); - const [accessToken] = useAccessToken(); - const isLoggedIn = !!accessToken; + const [auth] = useAuth(); + const isLoggedIn = !!auth.token; const [isOffline, setOffline] = useSharedState(IS_OFFLINE); const [isOutdated] = useSharedState(IS_OUTDATED); @@ -99,7 +103,7 @@ export const TestProviderRender = () => { const { startBuild, stopBuild } = useBuildEvents({ localBuildProgress, - accessToken, + accessToken: auth.token, }); let warning: string | undefined; diff --git a/src/components/FooterMenu.tsx b/src/components/FooterMenu.tsx index 966aaf27..f5a3cef3 100644 --- a/src/components/FooterMenu.tsx +++ b/src/components/FooterMenu.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { ActionList, PopoverProvider } from 'storybook/internal/components'; import { experimental_getStatusStore } from 'storybook/manager-api'; -import { useAuthState } from '../AuthContext'; import { ADDON_ID, PROJECT_INFO } from '../constants'; import { useControlsDispatch } from '../screens/VisualTests/ControlsContext'; import { ProjectInfoPayload } from '../types'; +import { useAuth } from '../utils/graphQLClient'; import { useSharedState } from '../utils/useSharedState'; export const FooterMenu = () => { - const { accessToken, setAccessToken, subdomain } = useAuthState(); + const [auth, setAuth] = useAuth(); const { toggleConfig } = useControlsDispatch(); const [projectInfo] = useSharedState(PROJECT_INFO); const statusStore = experimental_getStatusStore(ADDON_ID); @@ -54,7 +54,7 @@ export const FooterMenu = () => { @@ -66,13 +66,13 @@ export const FooterMenu = () => { )} - {accessToken && ( + {auth.token && ( { statusStore.unset(); - setAccessToken(null); + setAuth((s) => ({ ...s, token: null })); onHide(); }} > diff --git a/src/manager.tsx b/src/manager.tsx index 274901a5..59376dce 100644 --- a/src/manager.tsx +++ b/src/manager.tsx @@ -33,11 +33,11 @@ addons.register(ADDON_ID, (api) => { addons.add(SHARE_PROVIDER_ID, { type: Addon_TypesEnum.experimental_SHARE_PROVIDER, - shareMenu: () => , + shareMenu: () => , } satisfies Omit); addons.add(TEST_PROVIDER_ID, { type: Addon_TypesEnum.experimental_TEST_PROVIDER, - render: () => , + render: () => , } satisfies Omit); }); diff --git a/src/screens/Authentication/Authentication.tsx b/src/screens/Authentication/Authentication.tsx index 309ef3b1..7ff9a162 100644 --- a/src/screens/Authentication/Authentication.tsx +++ b/src/screens/Authentication/Authentication.tsx @@ -1,11 +1,10 @@ import React, { useCallback } from 'react'; -import { useAuthState } from '../../AuthContext'; import { Project } from '../../gql/graphql'; -import { initiateSignin, TokenExchangeParameters } from '../../utils/requestAccessToken'; +import { useAuth } from '../../utils/graphQLClient'; +import { initiateSignin } from '../../utils/requestAccessToken'; import { useTelemetry } from '../../utils/TelemetryContext'; import { useErrorNotification } from '../../utils/useErrorNotification'; -import { useSessionState } from '../../utils/useSessionState'; import { useUninstallAddon } from '../Uninstalled/UninstallContext'; import { SetSubdomain } from './SetSubdomain'; import { SignIn } from './SignIn'; @@ -13,74 +12,75 @@ import { Verify } from './Verify'; import { Welcome } from './Welcome'; interface AuthenticationProps { - setAccessToken: (token: string | null) => void; setCreatedProjectId: (projectId: Project['id']) => void; hasProjectId: boolean; } -type AuthenticationScreen = 'welcome' | 'signin' | 'subdomain' | 'verify'; - -export const Authentication = ({ - setAccessToken, - setCreatedProjectId, - hasProjectId, -}: AuthenticationProps) => { - const [screen, setScreen] = useSessionState( - 'authenticationScreen', - hasProjectId ? 'signin' : 'welcome' - ); - const [exchangeParameters, setExchangeParameters] = - useSessionState('exchangeParameters'); +export const Authentication = ({ setCreatedProjectId, hasProjectId }: AuthenticationProps) => { const onError = useErrorNotification(); const { uninstallAddon } = useUninstallAddon(); - const { setSubdomain } = useAuthState(); + const [auth, setAuth] = useAuth(); + + const screen = auth.screen; useTelemetry('Authentication', screen.charAt(0).toUpperCase() + screen.slice(1)); const initiateSignInAndMoveToVerify = useCallback( async (subdomain?: string) => { try { - setSubdomain(subdomain ?? 'www'); - setExchangeParameters(await initiateSignin(subdomain)); - setScreen('verify'); + const exchangeParameters = await initiateSignin(subdomain); + setAuth((s) => ({ + ...s, + subdomain: subdomain ?? 'www', + exchangeParameters, + screen: 'verify', + })); } catch (err: any) { onError('Sign in Error', err); } }, - [onError, setExchangeParameters, setScreen, setSubdomain] + [onError, setAuth] ); if (screen === 'welcome' && !hasProjectId) { - return setScreen('signin')} onUninstall={uninstallAddon} />; + return ( + setAuth((s) => ({ ...s, screen: 'signin' }))} + onUninstall={uninstallAddon} + /> + ); } if (screen === 'signin' || (screen === 'welcome' && hasProjectId)) { return ( setScreen('welcome') } : {})} + {...(!hasProjectId ? { onBack: () => setAuth((s) => ({ ...s, screen: 'welcome' })) } : {})} onSignIn={initiateSignInAndMoveToVerify} - onSignInWithSSO={() => setScreen('subdomain')} + onSignInWithSSO={() => setAuth((s) => ({ ...s, screen: 'subdomain' }))} /> ); } if (screen === 'subdomain') { return ( - setScreen('signin')} onSignIn={initiateSignInAndMoveToVerify} /> + setAuth((s) => ({ ...s, screen: 'signin' }))} + onSignIn={initiateSignInAndMoveToVerify} + /> ); } if (screen === 'verify') { - if (!exchangeParameters) { + if (!auth.exchangeParameters) { throw new Error('Expected to have a `exchangeParameters` if at `verify` step'); } return ( setScreen('signin')} + onBack={() => setAuth((s) => ({ ...s, screen: 'signin' }))} + setAuth={setAuth} hasProjectId={hasProjectId} - setAccessToken={setAccessToken} setCreatedProjectId={setCreatedProjectId} - exchangeParameters={exchangeParameters} + exchangeParameters={auth.exchangeParameters} /> ); } diff --git a/src/screens/Authentication/Verify.tsx b/src/screens/Authentication/Verify.tsx index 17d994da..03504fb9 100644 --- a/src/screens/Authentication/Verify.tsx +++ b/src/screens/Authentication/Verify.tsx @@ -10,7 +10,7 @@ import { Stack } from '../../components/Stack'; import { Text } from '../../components/Text'; import { graphql } from '../../gql'; import { Project } from '../../gql/graphql'; -import { getFetchOptions } from '../../utils/graphQLClient'; +import { getFetchOptions, useAuth } from '../../utils/graphQLClient'; import { fetchAccessToken, TokenExchangeParameters } from '../../utils/requestAccessToken'; import { DialogHandler, useChromaticDialog } from '../../utils/useChromaticDialog'; import { useErrorNotification } from '../../utils/useErrorNotification'; @@ -49,26 +49,25 @@ const ProjectCountQuery = graphql(/* GraphQL */ ` interface VerifyProps { onBack: () => void; hasProjectId: boolean; - setAccessToken: (token: string) => void; setCreatedProjectId: (projectId: Project['id']) => void; exchangeParameters: TokenExchangeParameters; + setAuth: ReturnType[1]; } export const Verify = ({ onBack, hasProjectId, - setAccessToken, + setAuth, setCreatedProjectId, exchangeParameters, }: VerifyProps) => { const client = useClient(); const onError = useErrorNotification(); - const { user_code: userCode, verificationUrl } = exchangeParameters; // Store the access token until we are ready to pass it to `setAccessToken` (at which point // the Panel will close the Authentication screen) - const accessToken = useRef(); + const accessToken = useRef(null); const openDialogRef = useRef<(url: string) => void>(); const closeDialogRef = useRef<() => void>(); @@ -95,7 +94,7 @@ export const Verify = ({ // The user has projects to choose from (or the project is already selected), // so send them to pick one if (data.viewer.projectCount > 0 || hasProjectId) { - setAccessToken(accessToken.current); + setAuth((s) => ({ ...s, token: accessToken.current })); closeDialogRef.current?.(); } else { // The user has no projects, so we need to get them to create one, then close the dialog @@ -115,7 +114,7 @@ export const Verify = ({ if (!accessToken.current) { onError('Unexpected missing access token', new Error()); } else { - setAccessToken(accessToken.current); + setAuth((s) => ({ ...s, token: accessToken.current })); setCreatedProjectId(`Project:${event.projectId}`); closeDialogRef.current?.(); } @@ -126,7 +125,7 @@ export const Verify = ({ exchangeParameters, client, hasProjectId, - setAccessToken, + setAuth, onError, setCreatedProjectId, ] diff --git a/src/screens/VisualTests/NoBuild.tsx b/src/screens/VisualTests/NoBuild.tsx index 99147705..7d25b4b2 100644 --- a/src/screens/VisualTests/NoBuild.tsx +++ b/src/screens/VisualTests/NoBuild.tsx @@ -5,7 +5,6 @@ import { useParameter } from 'storybook/manager-api'; import { styled } from 'storybook/theming'; import { CombinedError } from 'urql'; -import { useAuthState } from '../../AuthContext'; import { BuildProgressInline } from '../../components/BuildProgressBarInline'; import { Button } from '../../components/Button'; import { ButtonStack } from '../../components/ButtonStack'; @@ -19,6 +18,7 @@ import { Stack } from '../../components/Stack'; import { Text } from '../../components/Text'; import { DOCS_URL } from '../../constants'; import { LocalBuildProgress } from '../../types'; +import { useAuth } from '../../utils/graphQLClient'; import { ErrorBox } from '../Errors/BuildError'; import { useRunBuildState } from './RunBuildContext'; @@ -43,7 +43,7 @@ export const NoBuild = ({ localBuildProgress, branch, }: NoBuildProps) => { - const { setAccessToken } = useAuthState(); + const [, setAuth] = useAuth(); const { isRunning, startBuild } = useRunBuildState(); const { disable, disableSnapshot, docsOnly } = useParameter('chromatic', {} as any); @@ -89,7 +89,7 @@ export const NoBuild = ({ ariaLabel={false} size="medium" variant="solid" - onClick={() => setAccessToken(null)} + onClick={() => setAuth((s) => ({ ...s, token: null }))} > Log out @@ -115,7 +115,7 @@ export const NoBuild = ({ ariaLabel={false} size="medium" variant="solid" - onClick={() => setAccessToken(null)} + onClick={() => setAuth((s) => ({ ...s, token: null }))} > Log out @@ -143,7 +143,11 @@ export const NoBuild = ({ - setAccessToken(null)} withArrow> + setAuth((s) => ({ ...s, token: null }))} + withArrow + > Switch account diff --git a/src/utils/graphQLClient.tsx b/src/utils/graphQLClient.tsx index e75594b3..1e5b3769 100644 --- a/src/utils/graphQLClient.tsx +++ b/src/utils/graphQLClient.tsx @@ -1,14 +1,16 @@ import { authExchange } from '@urql/exchange-auth'; -import React from 'react'; +import React, { useEffect } from 'react'; import { useAddonState } from 'storybook/manager-api'; import { Client, ClientOptions, fetchExchange, mapExchange, Provider } from 'urql'; import { v4 as uuid } from 'uuid'; import { ACCESS_TOKEN_KEY, ADDON_ID, CHROMATIC_API_URL } from '../constants'; +import { TokenExchangeParameters } from './requestAccessToken'; +import { clearSessionState, useSessionState } from './useSessionState'; let currentToken: string | null; let currentTokenExpiration: number | null; -const setCurrentToken = (token: string | null) => { +const persistCurrentToken = (token: string | null) => { try { const { exp } = token ? JSON.parse(atob(token.split('.')[1])) : { exp: null }; currentToken = token; @@ -24,24 +26,46 @@ const setCurrentToken = (token: string | null) => { } }; -setCurrentToken(localStorage.getItem(ACCESS_TOKEN_KEY)); +persistCurrentToken(localStorage.getItem(ACCESS_TOKEN_KEY)); +interface AuthValue { + token: string | null; + isOpen: boolean; + subdomain: string; + screen: 'welcome' | 'signin' | 'subdomain' | 'verify'; + exchangeParameters: TokenExchangeParameters | null; +} + +export const useAuth = () => { + const [subdomain, setSubdomain] = useSessionState('subdomain', 'www'); + const [exchangeParameters, setExchangeParameters] = + useSessionState('exchangeParameters', null); -export const useAccessToken = () => { // We use an object rather than a straight boolean here due to https://github.com/storybookjs/storybook/pull/23991 - const [{ token }, setTokenState] = useAddonState<{ token: string | null }>( - `${ADDON_ID}/accessToken`, - { token: currentToken } - ); - - const updateToken = React.useCallback( - (newToken: string | null) => { - setCurrentToken(newToken); - setTokenState({ token: currentToken }); - }, - [setTokenState] - ); - - return [token, updateToken] as const; + const [auth, setAuth] = useAddonState(`${ADDON_ID}/auth`, { + token: currentToken, + isOpen: false, + subdomain: subdomain, + screen: 'welcome', + exchangeParameters, + }); + + useEffect(() => { + if (!auth.token) { + clearSessionState('authenticationScreen', 'exchangeParameters'); + } else { + persistCurrentToken(auth.token); + } + }, [auth.token]); + + useEffect(() => { + setSubdomain(auth.subdomain); + }, [auth.subdomain, setSubdomain]); + + useEffect(() => { + setExchangeParameters(auth.exchangeParameters); + }, [auth.exchangeParameters, setExchangeParameters]); + + return [auth, setAuth] as const; }; const sessionId = uuid(); @@ -63,7 +87,7 @@ export const createClient = (options?: Partial) => onResult(result) { // Not all queries contain the `viewer` field, in which case it will be `undefined`. // When we do retrieve the field but the token is invalid, it will be `null`. - if (result.data?.viewer === null) setCurrentToken(null); + if (result.data?.viewer === null) persistCurrentToken(null); }, }), authExchange(async (utils) => { @@ -81,7 +105,7 @@ export const createClient = (options?: Partial) => // If didAuthError returns true, clear the token. Ideally we should refresh the token here. // The operation will be retried automatically. async refreshAuth() { - setCurrentToken(null); + persistCurrentToken(null); }, // Prevent making a request if we know the token is missing, invalid or expired. From 12384a838a05b5a93c4bb5f19d20afaf555831ee Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 4 Feb 2026 13:43:11 +0100 Subject: [PATCH 04/11] Update dependencies and refactor authentication handling - Upgraded `chromatic` to version 14.0.0 and updated various Storybook-related dependencies to specific versions. - Introduced `ShareProviderRender` component to replace `ShareMenu`, enhancing the build sharing functionality. - Refactored authentication logic by moving `useAuth` to a new file, improving state management and modularity. - Removed the deprecated `ShareMenu` component and its associated stories. - Updated Storybook preview configuration to integrate new authentication and sharing features. --- .storybook/preview.tsx | 17 +- package.json | 12 +- src/Panel.tsx | 3 +- src/ShareMenu.stories.tsx | 26 --- src/ShareProviderRender.stories.tsx | 173 ++++++++++++++++++ ...{ShareMenu.tsx => ShareProviderRender.tsx} | 145 +++++++-------- src/TestProviderRender.tsx | 2 +- src/components/FooterMenu.tsx | 2 +- src/manager.tsx | 5 +- .../Authentication/Authentication.stories.tsx | 55 ++++-- src/screens/Authentication/Authentication.tsx | 2 +- src/screens/Authentication/Verify.tsx | 3 +- src/screens/VisualTests/NoBuild.tsx | 2 +- src/utils/graphQLClient.tsx | 51 +----- src/utils/useAuth.ts | 48 +++++ src/utils/useTestProviderStore.ts | 4 + yarn.lock | 110 +++++------ 17 files changed, 410 insertions(+), 250 deletions(-) delete mode 100644 src/ShareMenu.stories.tsx create mode 100644 src/ShareProviderRender.stories.tsx rename src/{ShareMenu.tsx => ShareProviderRender.tsx} (65%) create mode 100644 src/utils/useAuth.ts create mode 100644 src/utils/useTestProviderStore.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d73b1c4b..7e7f3e79 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,6 +1,6 @@ import { ManagerContext } from 'storybook/manager-api'; import type { Decorator, Loader, Preview } from '@storybook/react-vite'; -import { fn } from 'storybook/test'; +import { fn, sb } from 'storybook/test'; import { Global, ThemeProvider, @@ -14,7 +14,6 @@ import { HttpResponse, graphql } from 'msw'; import { initialize, mswLoader } from 'msw-storybook-addon'; import React from 'react'; -import { AuthProvider } from '../src/AuthContext'; import { baseModes } from '../src/modes'; import { UninstallProvider } from '../src/screens/Uninstalled/UninstallContext'; import { RunBuildProvider } from '../src/screens/VisualTests/RunBuildContext'; @@ -23,6 +22,10 @@ import { storyWrapper } from '../src/utils/storyWrapper'; import { TelemetryProvider } from '../src/utils/TelemetryContext'; import { useSessionState } from '../src/utils/useSessionState'; +sb.mock(import('../src/utils/useAuth.ts')) +sb.mock(import('../src/utils/useSharedState.ts')) +sb.mock(import('../src/utils/useTestProviderStore.ts')) + // Initialize MSW initialize({ onUnhandledRequest(req) { @@ -127,15 +130,6 @@ const withTelemetry = storyWrapper(TelemetryProvider, () => ({ value: fn().mockName('telemetry'), })); -const withAuth = storyWrapper(AuthProvider, () => ({ - value: { - accessToken: 'token', - setAccessToken: fn(), - subdomain: 'www', - setSubdomain: fn(), - }, -})); - const withManagerApi = storyWrapper(ManagerContext.Provider, ({ argsByTarget }) => ({ value: { api: { ...argsByTarget['manager-api'] }, @@ -213,7 +207,6 @@ const preview: Preview = { withTheme, withGraphQLClient, withTelemetry, - withAuth, withUninstall, withManagerApi, withRunBuild, diff --git a/package.json b/package.json index d249f913..39da8065 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ }, "dependencies": { "@neoconfetti/react": "^1.0.0", - "chromatic": "^13.3.4", + "chromatic": "^14.0.0", "filesize": "^10.0.12", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" @@ -68,9 +68,9 @@ "@graphql-typed-document-node/core": "^3.2.0", "@parcel/watcher": "^2.4.1", "@storybook/addon-designs": "^11.1.1", - "@storybook/addon-docs": "^10.2.1", + "@storybook/addon-docs": "0.0.0-pr-33653-sha-f37b6acd", "@storybook/icons": "^2.0.1", - "@storybook/react-vite": "^10.2.1", + "@storybook/react-vite": "0.0.0-pr-33653-sha-f37b6acd", "@types/jsonfile": "^6.1.1", "@types/node": "^22.13.5", "@types/pluralize": "^0.0.29", @@ -91,7 +91,7 @@ "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-storybook": "^10.2.1", + "eslint-plugin-storybook": "0.0.0-pr-33653-sha-f37b6acd", "graphql": "^16.8.1", "msw": "^2.0.0", "msw-storybook-addon": "^2.0.6", @@ -105,7 +105,7 @@ "react-dom": "^18.3.1", "react-joyride": "^2.7.2", "rimraf": "^3.0.2", - "storybook": "^10.2.1", + "storybook": "0.0.0-pr-33653-sha-f37b6acd", "ts-dedent": "^2.2.0", "tsup": "^6.6.3", "typescript": "^5.7.3", @@ -119,7 +119,7 @@ "zx": "^1.14.1" }, "peerDependencies": { - "storybook": "0.0.0-pr-33653-sha-709242df" + "storybook": "0.0.0-pr-33653-sha-f37b6acd" }, "packageManager": "yarn@4.12.0", "engines": { diff --git a/src/Panel.tsx b/src/Panel.tsx index 984a76e7..dc680ca0 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -26,8 +26,9 @@ import { ControlsProvider } from './screens/VisualTests/ControlsContext'; import { RunBuildProvider } from './screens/VisualTests/RunBuildContext'; import { VisualTests } from './screens/VisualTests/VisualTests'; import { GitInfoPayload, LocalBuildProgress, UpdateStatusFunction } from './types'; -import { createClient, GraphQLClientProvider, useAuth } from './utils/graphQLClient'; +import { createClient, GraphQLClientProvider } from './utils/graphQLClient'; import { TelemetryProvider } from './utils/TelemetryContext'; +import { useAuth } from './utils/useAuth'; import { useBuildEvents } from './utils/useBuildEvents'; import { useChannelFetch } from './utils/useChannelFetch'; import { useProjectId } from './utils/useProjectId'; diff --git a/src/ShareMenu.stories.tsx b/src/ShareMenu.stories.tsx deleted file mode 100644 index 209827d2..00000000 --- a/src/ShareMenu.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { fn } from 'storybook/test'; - -import { ShareMenu } from './ShareMenu'; - -const meta = { - component: ShareMenu, - args: { - api: {}, - getAddonState: fn(), - setAddonState: fn(), - on: fn(), - off: fn(), - }, - argTypes: { - getAddonState: { type: 'function', target: 'manager-api' }, - setAddonState: { type: 'function', target: 'manager-api' }, - on: { type: 'function', target: 'manager-api' }, - off: { type: 'function', target: 'manager-api' }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/src/ShareProviderRender.stories.tsx b/src/ShareProviderRender.stories.tsx new file mode 100644 index 00000000..0d046fdb --- /dev/null +++ b/src/ShareProviderRender.stories.tsx @@ -0,0 +1,173 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { API } from 'storybook/manager-api'; +import { fn, mocked } from 'storybook/test'; + +import { IS_OFFLINE, LOCAL_BUILD_PROGRESS, PROJECT_INFO, TEST_PROVIDER_ID } from './constants'; +import { ShareProviderRender } from './ShareProviderRender'; +import { GraphQLClientProvider } from './utils/graphQLClient'; +import { MockChannel } from './utils/MockChannel'; +import { storyWrapper } from './utils/storyWrapper'; +import { AuthValue, useAuth } from './utils/useAuth'; +import { useSharedState } from './utils/useSharedState'; +import { getTestProviderStore, useTestProviderStore } from './utils/useTestProviderStore'; + +const channel = new MockChannel(); + +const api = { + getChannel: () => channel, + getAddonState: fn(), + setAddonState: fn(), + on: fn(), + off: fn(), + emit: fn(), +} as unknown as API; + +const meta = { + component: ShareProviderRender, + args: { + ...api, + api, + auth: { + token: null, + isOpen: false, + subdomain: 'www', + screen: 'welcome', + exchangeParameters: null, + } as AuthValue, + [PROJECT_INFO]: undefined, + [LOCAL_BUILD_PROGRESS]: undefined, + [IS_OFFLINE]: false, + }, + argTypes: { + auth: { type: 'object', target: 'auth' }, + [TEST_PROVIDER_ID]: { + control: 'select', + options: [ + 'test-provider-state:pending', + 'test-provider-state:running', + 'test-provider-state:completed', + 'test-provider-state:aborted', + ], + target: 'test-provider-store', + }, + [LOCAL_BUILD_PROGRESS]: { type: 'object', target: 'shared-state' }, + [PROJECT_INFO]: { type: 'object', target: 'shared-state' }, + [IS_OFFLINE]: { type: 'boolean', target: 'shared-state' }, + getAddonState: { type: 'function', target: 'manager-api' }, + setAddonState: { type: 'function', target: 'manager-api' }, + on: { type: 'function', target: 'manager-api' }, + off: { type: 'function', target: 'manager-api' }, + emit: { type: 'function', target: 'manager-api' }, + }, + beforeEach: ({ argsByTarget }) => { + mocked(useAuth).mockImplementation(() => [ + { + token: null, + isOpen: false, + subdomain: 'www', + screen: 'welcome', + exchangeParameters: null, + ...argsByTarget['auth'].auth, + }, + fn().mockName(`setAuth`), + ]); + mocked(useSharedState).mockImplementation((key: string) => [ + argsByTarget['shared-state'][key], + fn().mockName(`set:${key}`), + ]); + mocked(useTestProviderStore).mockImplementation( + () => argsByTarget['test-provider-store']?.[TEST_PROVIDER_ID] ?? 'test-provider-state:pending' + ); + mocked(getTestProviderStore).mockImplementation(() => ({ + getState: () => + argsByTarget['test-provider-store']?.[TEST_PROVIDER_ID] ?? 'test-provider-state:pending', + setState: fn(), + runWithState: fn(), + testProviderId: 'test-provider-id', + onRunAll: fn(), + onClearAll: fn(), + settingsChanged: fn(), + })); + }, + decorators: [storyWrapper(GraphQLClientProvider)], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Setup: Story = {}; + +export const Login: Story = { + args: { + [PROJECT_INFO]: { projectId: '123' }, + }, +}; + +export const Ready: Story = { + args: { + ...Login.args, + auth: { token: 'test-token' }, + }, +}; + +export const Starting: Story = { + args: { + ...Ready.args, + [TEST_PROVIDER_ID]: 'test-provider-state:running', + }, +}; + +export const Uploading: Story = { + args: { + ...Starting.args, + [LOCAL_BUILD_PROGRESS]: { + currentStep: 'upload', + stepProgress: { upload: { numerator: 1_200_000, denominator: 2_300_000 } }, + }, + }, +}; + +export const Verifying: Story = { + args: { + ...Uploading.args, + [LOCAL_BUILD_PROGRESS]: { + currentStep: 'verify', + }, + }, +}; + +export const Completed: Story = { + args: { + ...Ready.args, + [TEST_PROVIDER_ID]: 'test-provider-state:succeeded', + [LOCAL_BUILD_PROGRESS]: { + currentStep: 'complete', + }, + }, +}; + +export const Crashed: Story = { + args: { + ...Ready.args, + [TEST_PROVIDER_ID]: 'test-provider-state:crashed', + [LOCAL_BUILD_PROGRESS]: { + currentStep: 'error', + }, + }, +}; + +export const Aborted: Story = { + args: { + ...Ready.args, + [LOCAL_BUILD_PROGRESS]: { + currentStep: 'aborted', + }, + }, +}; + +export const Offline: Story = { + args: { + ...Ready.args, + [IS_OFFLINE]: true, + }, +}; diff --git a/src/ShareMenu.tsx b/src/ShareProviderRender.tsx similarity index 65% rename from src/ShareMenu.tsx rename to src/ShareProviderRender.tsx index d2d23b4a..f0d790d0 100644 --- a/src/ShareMenu.tsx +++ b/src/ShareProviderRender.tsx @@ -1,80 +1,57 @@ import { FailedIcon } from '@storybook/icons'; -import { PlayHollowIcon, StopAltIcon } from '@storybook/icons'; import pluralize from 'pluralize'; -import React, { useCallback, useContext, useEffect, useRef } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Link } from 'storybook/internal/components'; -import { Button, ProgressSpinner } from 'storybook/internal/components'; -import { - API, - experimental_getTestProviderStore, - experimental_useStatusStore, - experimental_useTestProviderStore, - useStorybookApi, - useStorybookState, -} from 'storybook/manager-api'; +import { Button } from 'storybook/internal/components'; +import { API, useStorybookApi, useStorybookState } from 'storybook/manager-api'; import { color, styled } from 'storybook/theming'; import { BUILD_STEP_CONFIG } from './buildSteps'; -import { BuildProgressInline } from './components/BuildProgressBarInline'; import { ADDON_ID, CONFIG_INFO, GIT_INFO_ERROR, IS_OFFLINE, - IS_OUTDATED, LOCAL_BUILD_PROGRESS, - PANEL_ID, TEST_PROVIDER_ID, } from './constants'; import { ConfigInfoPayload, LocalBuildProgress } from './types'; -import { useAuth } from './utils/graphQLClient'; import { TelemetryContext } from './utils/TelemetryContext'; +import { useAuth } from './utils/useAuth'; import { useBuildEvents } from './utils/useBuildEvents'; import { useProjectId } from './utils/useProjectId'; import { useSharedState } from './utils/useSharedState'; +import { getTestProviderStore, useTestProviderStore } from './utils/useTestProviderStore'; -const Container = styled.div({ +const Container = styled.div(() => ({ display: 'flex', justifyContent: 'space-between', - padding: '8px 0', -}); + padding: 24, + maxWidth: 500, + gap: 16, +})); -const Info = styled.div({ +const Content = styled.div(({ theme }) => ({ display: 'flex', flexDirection: 'column', - marginLeft: 8, -}); - -const Actions = styled.div({ - display: 'flex', - gap: 4, -}); - -const TitleWrapper = styled.div<{ crashed?: boolean }>(({ crashed, theme }) => ({ + alignItems: 'flex-start', + gap: 8, fontSize: theme.typography.size.s1, - fontWeight: crashed ? 'bold' : 'normal', - color: crashed ? theme.color.negativeText : theme.color.defaultText, + color: theme.color.defaultText, })); -const DescriptionWrapper = styled.div(({ theme }) => ({ - fontSize: theme.typography.size.s1, - color: theme.textMutedColor, +const Title = styled.div(({ theme }) => ({ + fontWeight: theme.typography.weight.bold, + lineHeight: '20px', })); -const Progress = styled(ProgressSpinner)({ - margin: 4, -}); - -const StopIcon = styled(StopAltIcon)({ - width: 10, -}); - -interface Props { - api: API; -} +const Description = styled.div(({ theme }) => ({ + color: theme.textMutedColor, +})); -export const ShareMenu = ({ api }: Props) => { - const { addNotification } = api; +export const ShareProviderRender = ({ api }: { api: API }) => { + const { addNotification, getStoryHrefs } = api; + const { storyId } = useStorybookState(); const trackEvent = useContext(TelemetryContext); const { projectId } = useProjectId(); @@ -89,9 +66,11 @@ export const ShareMenu = ({ api }: Props) => { const [gitInfoError] = useSharedState(GIT_INFO_ERROR); + const [copied, setCopied] = useState(false); + const lastStep = useRef(localBuildProgress?.currentStep); - const testProviderState = experimental_useTestProviderStore( + const testProviderState = useTestProviderStore( (state) => state[TEST_PROVIDER_ID] ?? 'test-provider-state:pending' ); @@ -116,7 +95,7 @@ export const ShareMenu = ({ api }: Props) => { }, [isRunnable, startBuild]); useEffect( - () => experimental_getTestProviderStore(TEST_PROVIDER_ID).onRunAll(startBuildIfPossible), + () => getTestProviderStore(TEST_PROVIDER_ID).onRunAll(startBuildIfPossible), [startBuildIfPossible] ); @@ -173,9 +152,11 @@ export const ShareMenu = ({ api }: Props) => { description = {warning}; break; case testProviderState === 'test-provider-state:running': - description = localBuildProgress - ? BUILD_STEP_CONFIG[localBuildProgress.currentStep].renderProgress(localBuildProgress) - : 'Starting...'; + description = localBuildProgress?.storybookUrl + ? `Succesfully published` + : localBuildProgress + ? BUILD_STEP_CONFIG[localBuildProgress.currentStep].renderProgress(localBuildProgress) + : 'Starting...'; break; case localBuildProgress?.currentStep === 'aborted': description = 'Aborted by user'; @@ -183,55 +164,65 @@ export const ShareMenu = ({ api }: Props) => { case localBuildProgress?.currentStep === 'complete': description = localBuildProgress.errorCount ? `Encountered ${pluralize('component error', localBuildProgress.errorCount, true)}` - : `Published`; + : `Succesfully published`; break; default: - description = 'Not run'; + description = 'No snapshots will be taken'; } - if (localBuildProgress?.isPublishOnly === false) { - description = 'Visual tests in progress'; - } + const copyLink = () => { + const networkAddress = (globalThis as any).STORYBOOK_NETWORK_ADDRESS; + (globalThis as any).STORYBOOK_NETWORK_ADDRESS = localBuildProgress!.storybookUrl!; + const { managerHref } = getStoryHrefs(storyId, { base: 'network' }); + navigator.clipboard.writeText(managerHref); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + (globalThis as any).STORYBOOK_NETWORK_ADDRESS = networkAddress; + }; return ( - - {localBuildProgress?.isPublishOnly ? ( - - ) : ( - {description} - )} - - - - {warning ? null : testProviderState === 'test-provider-state:running' ? ( + +
+ Upload a build to share + {description} +
+ {localBuildProgress?.storybookUrl ? ( + + ) : warning ? null : testProviderState === 'test-provider-state:running' ? ( ) : ( )} -
+
); }; diff --git a/src/TestProviderRender.tsx b/src/TestProviderRender.tsx index a422cb2a..19b15095 100644 --- a/src/TestProviderRender.tsx +++ b/src/TestProviderRender.tsx @@ -25,8 +25,8 @@ import { TEST_PROVIDER_ID, } from './constants'; import { ConfigInfoPayload, LocalBuildProgress } from './types'; -import { useAuth } from './utils/graphQLClient'; import { TelemetryContext } from './utils/TelemetryContext'; +import { useAuth } from './utils/useAuth'; import { useBuildEvents } from './utils/useBuildEvents'; import { useProjectId } from './utils/useProjectId'; import { useSharedState } from './utils/useSharedState'; diff --git a/src/components/FooterMenu.tsx b/src/components/FooterMenu.tsx index f5a3cef3..a5c186f5 100644 --- a/src/components/FooterMenu.tsx +++ b/src/components/FooterMenu.tsx @@ -6,7 +6,7 @@ import { experimental_getStatusStore } from 'storybook/manager-api'; import { ADDON_ID, PROJECT_INFO } from '../constants'; import { useControlsDispatch } from '../screens/VisualTests/ControlsContext'; import { ProjectInfoPayload } from '../types'; -import { useAuth } from '../utils/graphQLClient'; +import { useAuth } from '../utils/useAuth'; import { useSharedState } from '../utils/useSharedState'; export const FooterMenu = () => { diff --git a/src/manager.tsx b/src/manager.tsx index 59376dce..88549f75 100644 --- a/src/manager.tsx +++ b/src/manager.tsx @@ -8,7 +8,7 @@ import { addons, experimental_getStatusStore } from 'storybook/manager-api'; import { ADDON_ID, PANEL_ID, PARAM_KEY, SHARE_PROVIDER_ID, TEST_PROVIDER_ID } from './constants.ts'; import { Panel } from './Panel'; -import { ShareMenu } from './ShareMenu.tsx'; +import { ShareProviderRender } from './ShareProviderRender'; import { TestProviderRender } from './TestProviderRender'; addons.register(ADDON_ID, (api) => { @@ -33,7 +33,8 @@ addons.register(ADDON_ID, (api) => { addons.add(SHARE_PROVIDER_ID, { type: Addon_TypesEnum.experimental_SHARE_PROVIDER, - shareMenu: () => , + title: 'Upload & share', + render: () => , } satisfies Omit); addons.add(TEST_PROVIDER_ID, { diff --git a/src/screens/Authentication/Authentication.stories.tsx b/src/screens/Authentication/Authentication.stories.tsx index 2cdf6773..476f6891 100644 --- a/src/screens/Authentication/Authentication.stories.tsx +++ b/src/screens/Authentication/Authentication.stories.tsx @@ -1,12 +1,12 @@ // @ts-nocheck TODO: Address SB 8 type errors import type { Meta, StoryObj } from '@storybook/react-vite'; import { http, HttpResponse } from 'msw'; -import { findByRole, fn, userEvent } from 'storybook/test'; +import { fn, mocked } from 'storybook/test'; import { panelModes } from '../../modes'; import { GraphQLClientProvider } from '../../utils/graphQLClient'; -import { playAll } from '../../utils/playAll'; import { storyWrapper } from '../../utils/storyWrapper'; +import { AuthValue, useAuth } from '../../utils/useAuth'; import { clearSessionState } from '../../utils/useSessionState'; import { withFigmaDesign } from '../../utils/withFigmaDesign'; import { withSetup } from '../../utils/withSetup'; @@ -19,6 +19,23 @@ const meta = { setAccessToken: fn().mockName('setAccessToken'), hasProjectId: false, }, + argTypes: { + auth: { + control: 'object', + target: 'auth', + }, + }, + beforeEach: ({ argsByTarget }) => { + const auth: AuthValue = { + token: 'token', + isOpen: false, + subdomain: 'www', + screen: 'welcome', + exchangeParameters: null, + ...argsByTarget['auth']?.auth, + }; + mocked(useAuth).mockImplementation(() => [auth, fn().mockName('setAuth')]); + }, parameters: { chromatic: { modes: panelModes, @@ -66,37 +83,35 @@ export const HasProjectId = { } satisfies Story; export const SignIn = { + args: { + auth: { screen: 'signin' }, + }, parameters: withFigmaDesign( 'https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=304-317993&t=3EAIRe8423CpOQWY-4' ), - play: playAll(async ({ canvasElement }) => { - const button = await findByRole(canvasElement, 'button', { - name: /Get started/, - }); - await userEvent.click(button); - }), } satisfies Story; export const SSO = { + args: { + auth: { screen: 'subdomain' }, + }, parameters: withFigmaDesign( 'https://www.figma.com/file/p4ZIW7diUWC2l2DAf5xpYI/Storybook-Connect-plugin-(EXTERNAL-USE)?type=design&node-id=1-1734&t=ysgtc5qR40kqRKtI-4' ), - play: playAll(SignIn, async (context) => { - const button = await findByRole(context.canvasElement, 'button', { - name: 'Sign in with SSO', - }); - await userEvent.click(button); - }), } satisfies Story; export const Verify = { + args: { + auth: { + screen: 'verify', + exchangeParameters: { + user_code: '123123', + verificationUrl: + 'https://www.chromatic.com/connect/chromaui:addon-visual-tests?code=123123', + }, + }, + }, parameters: withFigmaDesign( 'https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=304-318063&t=3EAIRe8423CpOQWY-4' ), - play: playAll(SignIn, async (context) => { - const button = await findByRole(context.canvasElement, 'button', { - name: 'Sign in with Chromatic', - }); - await userEvent.click(button); - }), } satisfies Story; diff --git a/src/screens/Authentication/Authentication.tsx b/src/screens/Authentication/Authentication.tsx index 7ff9a162..d31dd4ab 100644 --- a/src/screens/Authentication/Authentication.tsx +++ b/src/screens/Authentication/Authentication.tsx @@ -1,9 +1,9 @@ import React, { useCallback } from 'react'; import { Project } from '../../gql/graphql'; -import { useAuth } from '../../utils/graphQLClient'; import { initiateSignin } from '../../utils/requestAccessToken'; import { useTelemetry } from '../../utils/TelemetryContext'; +import { useAuth } from '../../utils/useAuth'; import { useErrorNotification } from '../../utils/useErrorNotification'; import { useUninstallAddon } from '../Uninstalled/UninstallContext'; import { SetSubdomain } from './SetSubdomain'; diff --git a/src/screens/Authentication/Verify.tsx b/src/screens/Authentication/Verify.tsx index 03504fb9..b2fc72d9 100644 --- a/src/screens/Authentication/Verify.tsx +++ b/src/screens/Authentication/Verify.tsx @@ -10,8 +10,9 @@ import { Stack } from '../../components/Stack'; import { Text } from '../../components/Text'; import { graphql } from '../../gql'; import { Project } from '../../gql/graphql'; -import { getFetchOptions, useAuth } from '../../utils/graphQLClient'; +import { getFetchOptions } from '../../utils/graphQLClient'; import { fetchAccessToken, TokenExchangeParameters } from '../../utils/requestAccessToken'; +import { useAuth } from '../../utils/useAuth'; import { DialogHandler, useChromaticDialog } from '../../utils/useChromaticDialog'; import { useErrorNotification } from '../../utils/useErrorNotification'; import { AuthHeader } from './AuthHeader'; diff --git a/src/screens/VisualTests/NoBuild.tsx b/src/screens/VisualTests/NoBuild.tsx index 7d25b4b2..db7d54d4 100644 --- a/src/screens/VisualTests/NoBuild.tsx +++ b/src/screens/VisualTests/NoBuild.tsx @@ -18,7 +18,7 @@ import { Stack } from '../../components/Stack'; import { Text } from '../../components/Text'; import { DOCS_URL } from '../../constants'; import { LocalBuildProgress } from '../../types'; -import { useAuth } from '../../utils/graphQLClient'; +import { useAuth } from '../../utils/useAuth'; import { ErrorBox } from '../Errors/BuildError'; import { useRunBuildState } from './RunBuildContext'; diff --git a/src/utils/graphQLClient.tsx b/src/utils/graphQLClient.tsx index 1e5b3769..35583095 100644 --- a/src/utils/graphQLClient.tsx +++ b/src/utils/graphQLClient.tsx @@ -1,16 +1,15 @@ import { authExchange } from '@urql/exchange-auth'; -import React, { useEffect } from 'react'; -import { useAddonState } from 'storybook/manager-api'; +import React from 'react'; import { Client, ClientOptions, fetchExchange, mapExchange, Provider } from 'urql'; import { v4 as uuid } from 'uuid'; -import { ACCESS_TOKEN_KEY, ADDON_ID, CHROMATIC_API_URL } from '../constants'; -import { TokenExchangeParameters } from './requestAccessToken'; -import { clearSessionState, useSessionState } from './useSessionState'; +import { ACCESS_TOKEN_KEY, CHROMATIC_API_URL } from '../constants'; let currentToken: string | null; let currentTokenExpiration: number | null; -const persistCurrentToken = (token: string | null) => { + +export const getCurrentToken = () => currentToken; +export const persistCurrentToken = (token: string | null) => { try { const { exp } = token ? JSON.parse(atob(token.split('.')[1])) : { exp: null }; currentToken = token; @@ -27,46 +26,6 @@ const persistCurrentToken = (token: string | null) => { }; persistCurrentToken(localStorage.getItem(ACCESS_TOKEN_KEY)); -interface AuthValue { - token: string | null; - isOpen: boolean; - subdomain: string; - screen: 'welcome' | 'signin' | 'subdomain' | 'verify'; - exchangeParameters: TokenExchangeParameters | null; -} - -export const useAuth = () => { - const [subdomain, setSubdomain] = useSessionState('subdomain', 'www'); - const [exchangeParameters, setExchangeParameters] = - useSessionState('exchangeParameters', null); - - // We use an object rather than a straight boolean here due to https://github.com/storybookjs/storybook/pull/23991 - const [auth, setAuth] = useAddonState(`${ADDON_ID}/auth`, { - token: currentToken, - isOpen: false, - subdomain: subdomain, - screen: 'welcome', - exchangeParameters, - }); - - useEffect(() => { - if (!auth.token) { - clearSessionState('authenticationScreen', 'exchangeParameters'); - } else { - persistCurrentToken(auth.token); - } - }, [auth.token]); - - useEffect(() => { - setSubdomain(auth.subdomain); - }, [auth.subdomain, setSubdomain]); - - useEffect(() => { - setExchangeParameters(auth.exchangeParameters); - }, [auth.exchangeParameters, setExchangeParameters]); - - return [auth, setAuth] as const; -}; const sessionId = uuid(); diff --git a/src/utils/useAuth.ts b/src/utils/useAuth.ts new file mode 100644 index 00000000..e2255fe8 --- /dev/null +++ b/src/utils/useAuth.ts @@ -0,0 +1,48 @@ +import { useEffect } from 'react'; +import { useAddonState } from 'storybook/manager-api'; + +import { ADDON_ID } from '../constants'; +import { getCurrentToken, persistCurrentToken } from './graphQLClient'; +import { TokenExchangeParameters } from './requestAccessToken'; +import { clearSessionState, useSessionState } from './useSessionState'; + +export interface AuthValue { + token: string | null; + isOpen: boolean; + subdomain: string; + screen: 'welcome' | 'signin' | 'subdomain' | 'verify'; + exchangeParameters: TokenExchangeParameters | null; +} + +export const useAuth = () => { + const [subdomain, setSubdomain] = useSessionState('subdomain', 'www'); + const [exchangeParameters, setExchangeParameters] = + useSessionState('exchangeParameters', null); + + // We use an object rather than a straight boolean here due to https://github.com/storybookjs/storybook/pull/23991 + const [auth, setAuth] = useAddonState(`${ADDON_ID}/auth`, { + token: getCurrentToken(), + isOpen: false, + subdomain: subdomain, + screen: 'welcome', + exchangeParameters, + }); + + useEffect(() => { + if (!auth.token) { + clearSessionState('authenticationScreen', 'exchangeParameters'); + } else { + persistCurrentToken(auth.token); + } + }, [auth.token]); + + useEffect(() => { + setSubdomain(auth.subdomain); + }, [auth.subdomain, setSubdomain]); + + useEffect(() => { + setExchangeParameters(auth.exchangeParameters); + }, [auth.exchangeParameters, setExchangeParameters]); + + return [auth, setAuth] as const; +}; diff --git a/src/utils/useTestProviderStore.ts b/src/utils/useTestProviderStore.ts new file mode 100644 index 00000000..30f01c25 --- /dev/null +++ b/src/utils/useTestProviderStore.ts @@ -0,0 +1,4 @@ +export { + experimental_getTestProviderStore as getTestProviderStore, + experimental_useTestProviderStore as useTestProviderStore, +} from 'storybook/manager-api'; diff --git a/yarn.lock b/yarn.lock index b57983cf..4abb4d5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -452,9 +452,9 @@ __metadata: "@neoconfetti/react": "npm:^1.0.0" "@parcel/watcher": "npm:^2.4.1" "@storybook/addon-designs": "npm:^11.1.1" - "@storybook/addon-docs": "npm:^10.2.1" + "@storybook/addon-docs": "npm:0.0.0-pr-33653-sha-f37b6acd" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-vite": "npm:^10.2.1" + "@storybook/react-vite": "npm:0.0.0-pr-33653-sha-f37b6acd" "@types/jsonfile": "npm:^6.1.1" "@types/node": "npm:^22.13.5" "@types/pluralize": "npm:^0.0.29" @@ -467,7 +467,7 @@ __metadata: "@vitest/coverage-v8": "npm:^3.0.8" auto: "npm:^11.0.5" boxen: "npm:^5.0.1" - chromatic: "npm:^13.3.4" + chromatic: "npm:^14.0.0" date-fns: "npm:^2.30.0" dedent: "npm:^0.7.0" eslint: "npm:^9.21.0" @@ -476,7 +476,7 @@ __metadata: eslint-plugin-prettier: "npm:^5.2.3" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" - eslint-plugin-storybook: "npm:^10.2.1" + eslint-plugin-storybook: "npm:0.0.0-pr-33653-sha-f37b6acd" filesize: "npm:^10.0.12" graphql: "npm:^16.8.1" jsonfile: "npm:^6.1.0" @@ -492,7 +492,7 @@ __metadata: react-dom: "npm:^18.3.1" react-joyride: "npm:^2.7.2" rimraf: "npm:^3.0.2" - storybook: "npm:^10.2.1" + storybook: "npm:0.0.0-pr-33653-sha-f37b6acd" strip-ansi: "npm:^7.1.0" ts-dedent: "npm:^2.2.0" tsup: "npm:^6.6.3" @@ -506,7 +506,7 @@ __metadata: zod: "npm:^3.22.2" zx: "npm:^1.14.1" peerDependencies: - storybook: 0.0.0-pr-33653-sha-709242df + storybook: 0.0.0-pr-33653-sha-f37b6acd languageName: unknown linkType: soft @@ -2725,45 +2725,45 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-docs@npm:^10.2.1": - version: 10.2.1 - resolution: "@storybook/addon-docs@npm:10.2.1" +"@storybook/addon-docs@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/addon-docs@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:10.2.1" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-f37b6acd" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-dom-shim": "npm:10.2.1" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-f37b6acd" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.2.1 - checksum: 10c0/0592910380b0d366f90ef81c86cc46c615920b2057913e403a9bde2885d81de5155243ad83a32a4292472f370a08956d43816bccdde11880c19fb076549b17f9 + storybook: ^0.0.0-pr-33653-sha-f37b6acd + checksum: 10c0/63efe723b2be8ac76609ed6b3c1d0e5b28087c9c5972b3b7e2e6106906ff7f89988a19220d8146ee965c38e27f5f5d70640f99f0cbaa9298ee635fd7df0b7407 languageName: node linkType: hard -"@storybook/builder-vite@npm:10.2.1": - version: 10.2.1 - resolution: "@storybook/builder-vite@npm:10.2.1" +"@storybook/builder-vite@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/builder-vite@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: - "@storybook/csf-plugin": "npm:10.2.1" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-f37b6acd" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^10.2.1 + storybook: ^0.0.0-pr-33653-sha-f37b6acd vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/119d235ce358528a383aa98af664d252a3557c7e34923411025636b35586c21b4777d256dd3a5bed7401fc09e3c06b1355985c16bb583dba925e329aa1c0e156 + checksum: 10c0/914ba49e9a827686e49f82630af6f02c6cc43465d00107dac2d9eb12a5985595c62898b01ea63fe345a3a1d570035a0ed40de8f51fd40afd5dd0d568c5730d21 languageName: node linkType: hard -"@storybook/csf-plugin@npm:10.2.1": - version: 10.2.1 - resolution: "@storybook/csf-plugin@npm:10.2.1" +"@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: unplugin: "npm:^2.3.5" peerDependencies: esbuild: "*" rollup: "*" - storybook: ^10.2.1 + storybook: ^0.0.0-pr-33653-sha-f37b6acd vite: "*" webpack: "*" peerDependenciesMeta: @@ -2775,7 +2775,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10c0/7eb06fff05d2e0efa8fede3c3f2f7a8045464ceaec37aca48bc1ee046719bc225f7fb0ac8cd7b83e8fe25400ab3a25f967fc9d302f46f4363faa445af0e9dbf4 + checksum: 10c0/c22201322436fce43ac2d6da4479a6ddc70ce4077faf629099c276bcbec6d0ddb128effb7b9b303f8087ba550de6312833b8a2a4255820bf1ab8fc7c1fb93df3 languageName: node linkType: hard @@ -2796,25 +2796,25 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:10.2.1": - version: 10.2.1 - resolution: "@storybook/react-dom-shim@npm:10.2.1" +"@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-f37b6acd" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.2.1 - checksum: 10c0/fd675ac99a8fec0e20da3aceabda8a9f307f0d8b30c74eb80081fe79143bdf61001357439fa7c30106be7977f76aa556f3890d57b1b59113037c9438e4231749 + storybook: ^0.0.0-pr-33653-sha-f37b6acd + checksum: 10c0/f7ba4792c0d1b041e10986d28cae248eb9f53c5d8a8115e26c8e7011344e316973b31ddc56d19216f5e7c3cfaf840b0cd7c683b265c1bd37b8c83f28b1f7ca91 languageName: node linkType: hard -"@storybook/react-vite@npm:^10.2.1": - version: 10.2.1 - resolution: "@storybook/react-vite@npm:10.2.1" +"@storybook/react-vite@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/react-vite@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:10.2.1" - "@storybook/react": "npm:10.2.1" + "@storybook/builder-vite": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/react": "npm:0.0.0-pr-33653-sha-f37b6acd" empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -2823,28 +2823,28 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.2.1 + storybook: ^0.0.0-pr-33653-sha-f37b6acd vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/8fcd4a3eee67b363fd7f6f9d72d16a2037ab191c95161b9d7b2fe204299c5a9afed094dcaedb9309df02fa95b954ad7cd22af4555f0855f04ef79e0714d96190 + checksum: 10c0/cf48a06d55cb39e7a1a34b637381c159719685a9bd0637d9b5138b7af0c3aab46c1df636cdbf2a183c7a592ba1a86f06822ed47bf36a5ca897d7894fa49ecf4a languageName: node linkType: hard -"@storybook/react@npm:10.2.1": - version: 10.2.1 - resolution: "@storybook/react@npm:10.2.1" +"@storybook/react@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "@storybook/react@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:10.2.1" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-f37b6acd" react-docgen: "npm:^8.0.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^10.2.1 + storybook: ^0.0.0-pr-33653-sha-f37b6acd typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/dd3cdd61e4d2cad6cae7f4aa571decb7619a99768983f7c9e6ebb03434287b353234c6a416ec8cb89f8bfbb4b82c879b540caf41e4c2b4110425b00202028935 + checksum: 10c0/d85e813b6543481cd3ccac529c314609e848b44a107564d0a6631cc58fba4510a8950b5d67a366122a575f9d2dfd21fcbbaa49edb203b2a45b9dddfe39a568c9 languageName: node linkType: hard @@ -4252,9 +4252,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^13.3.4": - version: 13.3.4 - resolution: "chromatic@npm:13.3.4" +"chromatic@npm:^14.0.0": + version: 14.0.0 + resolution: "chromatic@npm:14.0.0" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -4267,7 +4267,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/1800c1640dbc168b621daeca5895698cb5a0a1def50b9d1ada5ea99ce242bf1f70d15065460948b168eedea1f56422553184f4cce1d01a7816f32c60054d704d + checksum: 10c0/ec3c1ae7ace74150a8a32b46bf57d7c89266ba9795d8504b8f174cf8f773ded2b2197f3e329f79cb7b06cbe694d2cc37660cc779d720c2739c29abfa2f558baf languageName: node linkType: hard @@ -5405,15 +5405,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:^10.2.1": - version: 10.2.1 - resolution: "eslint-plugin-storybook@npm:10.2.1" +"eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^10.2.1 - checksum: 10c0/c60f7722daed8aa0859ea5a36bfdcf7abea80ace9fa96629ab50d0fc700343f7a7a07216aecf630077ea0c613b432425b243eaff1e7460cc1c4e6650f21881c7 + storybook: ^0.0.0-pr-33653-sha-f37b6acd + checksum: 10c0/b851e44033965199fb2a90e6979bc60f1a713737e4305e21815372fd8be7981bd9e9fda6adcac95f420f9d0458b1538c931b01e03dbea4532012c452d7e2c96d languageName: node linkType: hard @@ -9549,9 +9549,9 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^10.2.1": - version: 10.2.1 - resolution: "storybook@npm:10.2.1" +"storybook@npm:0.0.0-pr-33653-sha-f37b6acd": + version: 0.0.0-pr-33653-sha-f37b6acd + resolution: "storybook@npm:0.0.0-pr-33653-sha-f37b6acd" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.1" @@ -9572,7 +9572,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10c0/e749d291e597478385e638aec96c7670d2d1cf0d490ffc0f6d918aaf2f8949201074b585908a6519649cc92c8fa54fa59abb054d80b415309b47d0fc3a1ea648 + checksum: 10c0/fb17c77bbe930e9c929be5e77e67ef628c4a2b088559f6cd9e9dfaaf579f218636662254d105e9fa01fa0d6b6dad822d2fcfa8465f5591107563e76c4cd16ff5 languageName: node linkType: hard From dccecea6d95a280b6ace942fe8adc61eb0488f17 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 13:55:14 +0100 Subject: [PATCH 05/11] optimize --- src/preset.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/preset.ts b/src/preset.ts index 4960eb1f..2306f17f 100644 --- a/src/preset.ts +++ b/src/preset.ts @@ -172,6 +172,7 @@ const watchConfigFile = async ( async function serverChannel(channel: Channel, options: Options & { configFile?: string }) { const { configFile, presets } = options; + const addonVersion = await getAddonVersion().catch(() => null); // Handle relayed fetch requests from the client ChannelFetch.subscribe(ADDON_ID, channel); @@ -249,7 +250,7 @@ async function serverChannel(channel: Channel, options: Options & { configFile?: channel.on(TELEMETRY, async (event: Event) => { if ((await corePromise).disableTelemetry) return; - telemetry('addon-visual-tests' as any, { ...event, addonVersion: await getAddonVersion() }); + telemetry('addon-visual-tests' as any, { ...event, addonVersion }); }); const configInfoState = SharedState.subscribe(CONFIG_INFO, channel); From 064cf748a135d7fa2bd2e8a3c8804b0b639cdbbf Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 14:35:27 +0100 Subject: [PATCH 06/11] add type to import --- src/screens/Authentication/Authentication.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/Authentication/Authentication.stories.tsx b/src/screens/Authentication/Authentication.stories.tsx index 476f6891..2a382aa7 100644 --- a/src/screens/Authentication/Authentication.stories.tsx +++ b/src/screens/Authentication/Authentication.stories.tsx @@ -6,7 +6,7 @@ import { fn, mocked } from 'storybook/test'; import { panelModes } from '../../modes'; import { GraphQLClientProvider } from '../../utils/graphQLClient'; import { storyWrapper } from '../../utils/storyWrapper'; -import { AuthValue, useAuth } from '../../utils/useAuth'; +import { type AuthValue, useAuth } from '../../utils/useAuth'; import { clearSessionState } from '../../utils/useSessionState'; import { withFigmaDesign } from '../../utils/withFigmaDesign'; import { withSetup } from '../../utils/withSetup'; From fb76b5816a69813644c3a2ca22d197f006dc9fb0 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 14:37:32 +0100 Subject: [PATCH 07/11] add storybook files to ts include --- .storybook/preview.tsx | 18 +++++++++--------- tsconfig.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 7e7f3e79..7f2cce85 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,18 +1,18 @@ -import { ManagerContext } from 'storybook/manager-api'; import type { Decorator, Loader, Preview } from '@storybook/react-vite'; +import { graphql, HttpResponse } from 'msw'; +import { initialize, mswLoader } from 'msw-storybook-addon'; +import React from 'react'; +import { ManagerContext } from 'storybook/manager-api'; import { fn, sb } from 'storybook/test'; import { - Global, - ThemeProvider, convert, createReset, + Global, styled, + ThemeProvider, themes, useTheme, } from 'storybook/theming'; -import { HttpResponse, graphql } from 'msw'; -import { initialize, mswLoader } from 'msw-storybook-addon'; -import React from 'react'; import { baseModes } from '../src/modes'; import { UninstallProvider } from '../src/screens/Uninstalled/UninstallContext'; @@ -22,9 +22,9 @@ import { storyWrapper } from '../src/utils/storyWrapper'; import { TelemetryProvider } from '../src/utils/TelemetryContext'; import { useSessionState } from '../src/utils/useSessionState'; -sb.mock(import('../src/utils/useAuth.ts')) -sb.mock(import('../src/utils/useSharedState.ts')) -sb.mock(import('../src/utils/useTestProviderStore.ts')) +sb.mock(import('../src/utils/useAuth.ts')); +sb.mock(import('../src/utils/useSharedState.ts')); +sb.mock(import('../src/utils/useTestProviderStore.ts')); // Initialize MSW initialize({ diff --git a/tsconfig.json b/tsconfig.json index 5bcad7df..15512b93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "module": "esnext", "noImplicitAny": true, "strict": true, - "rootDir": "./src", + "rootDir": ".", "skipLibCheck": true, "target": "ES2020", "plugins": [ @@ -25,5 +25,5 @@ } ] }, - "include": ["src/**/*", "types.d.ts"] + "include": ["src/**/*", "types.d.ts", ".storybook/**/*"] } From 8ea817924b38dd170b9751ef2c88bd427b65eab3 Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 14:40:28 +0100 Subject: [PATCH 08/11] fix stories --- src/screens/Authentication/Authentication.stories.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/screens/Authentication/Authentication.stories.tsx b/src/screens/Authentication/Authentication.stories.tsx index 2a382aa7..13470352 100644 --- a/src/screens/Authentication/Authentication.stories.tsx +++ b/src/screens/Authentication/Authentication.stories.tsx @@ -8,6 +8,7 @@ import { GraphQLClientProvider } from '../../utils/graphQLClient'; import { storyWrapper } from '../../utils/storyWrapper'; import { type AuthValue, useAuth } from '../../utils/useAuth'; import { clearSessionState } from '../../utils/useSessionState'; +import { useSharedState } from '../../utils/useSharedState'; import { withFigmaDesign } from '../../utils/withFigmaDesign'; import { withSetup } from '../../utils/withSetup'; import { Authentication } from './Authentication'; @@ -35,6 +36,7 @@ const meta = { ...argsByTarget['auth']?.auth, }; mocked(useAuth).mockImplementation(() => [auth, fn().mockName('setAuth')]); + mocked(useSharedState).mockImplementation((key: string) => [null, fn().mockName(`set:${key}`)]); }, parameters: { chromatic: { From 54ba3404888e6ada936db326535a36bc23c74b0d Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 17:25:43 +0100 Subject: [PATCH 09/11] Update Storybook dependencies to latest SHA references in package.json and yarn.lock --- package.json | 10 +++--- yarn.lock | 100 +++++++++++++++++++++++++-------------------------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index 39da8065..65320887 100644 --- a/package.json +++ b/package.json @@ -68,9 +68,9 @@ "@graphql-typed-document-node/core": "^3.2.0", "@parcel/watcher": "^2.4.1", "@storybook/addon-designs": "^11.1.1", - "@storybook/addon-docs": "0.0.0-pr-33653-sha-f37b6acd", + "@storybook/addon-docs": "0.0.0-pr-33653-sha-6047da63", "@storybook/icons": "^2.0.1", - "@storybook/react-vite": "0.0.0-pr-33653-sha-f37b6acd", + "@storybook/react-vite": "0.0.0-pr-33653-sha-6047da63", "@types/jsonfile": "^6.1.1", "@types/node": "^22.13.5", "@types/pluralize": "^0.0.29", @@ -91,7 +91,7 @@ "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-simple-import-sort": "^12.1.1", - "eslint-plugin-storybook": "0.0.0-pr-33653-sha-f37b6acd", + "eslint-plugin-storybook": "0.0.0-pr-33653-sha-6047da63", "graphql": "^16.8.1", "msw": "^2.0.0", "msw-storybook-addon": "^2.0.6", @@ -105,7 +105,7 @@ "react-dom": "^18.3.1", "react-joyride": "^2.7.2", "rimraf": "^3.0.2", - "storybook": "0.0.0-pr-33653-sha-f37b6acd", + "storybook": "0.0.0-pr-33653-sha-6047da63", "ts-dedent": "^2.2.0", "tsup": "^6.6.3", "typescript": "^5.7.3", @@ -119,7 +119,7 @@ "zx": "^1.14.1" }, "peerDependencies": { - "storybook": "0.0.0-pr-33653-sha-f37b6acd" + "storybook": "0.0.0-pr-33653-sha-6047da63" }, "packageManager": "yarn@4.12.0", "engines": { diff --git a/yarn.lock b/yarn.lock index 4abb4d5c..eeafea57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -452,9 +452,9 @@ __metadata: "@neoconfetti/react": "npm:^1.0.0" "@parcel/watcher": "npm:^2.4.1" "@storybook/addon-designs": "npm:^11.1.1" - "@storybook/addon-docs": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/addon-docs": "npm:0.0.0-pr-33653-sha-6047da63" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-vite": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/react-vite": "npm:0.0.0-pr-33653-sha-6047da63" "@types/jsonfile": "npm:^6.1.1" "@types/node": "npm:^22.13.5" "@types/pluralize": "npm:^0.0.29" @@ -476,7 +476,7 @@ __metadata: eslint-plugin-prettier: "npm:^5.2.3" eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-simple-import-sort: "npm:^12.1.1" - eslint-plugin-storybook: "npm:0.0.0-pr-33653-sha-f37b6acd" + eslint-plugin-storybook: "npm:0.0.0-pr-33653-sha-6047da63" filesize: "npm:^10.0.12" graphql: "npm:^16.8.1" jsonfile: "npm:^6.1.0" @@ -492,7 +492,7 @@ __metadata: react-dom: "npm:^18.3.1" react-joyride: "npm:^2.7.2" rimraf: "npm:^3.0.2" - storybook: "npm:0.0.0-pr-33653-sha-f37b6acd" + storybook: "npm:0.0.0-pr-33653-sha-6047da63" strip-ansi: "npm:^7.1.0" ts-dedent: "npm:^2.2.0" tsup: "npm:^6.6.3" @@ -506,7 +506,7 @@ __metadata: zod: "npm:^3.22.2" zx: "npm:^1.14.1" peerDependencies: - storybook: 0.0.0-pr-33653-sha-f37b6acd + storybook: 0.0.0-pr-33653-sha-6047da63 languageName: unknown linkType: soft @@ -2725,45 +2725,45 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-docs@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/addon-docs@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/addon-docs@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/addon-docs@npm:0.0.0-pr-33653-sha-6047da63" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-6047da63" "@storybook/icons": "npm:^2.0.1" - "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-6047da63" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^0.0.0-pr-33653-sha-f37b6acd - checksum: 10c0/63efe723b2be8ac76609ed6b3c1d0e5b28087c9c5972b3b7e2e6106906ff7f89988a19220d8146ee965c38e27f5f5d70640f99f0cbaa9298ee635fd7df0b7407 + storybook: ^0.0.0-pr-33653-sha-6047da63 + checksum: 10c0/ac62129a9a8794b76480ccd5892ec765c1c7b26ab7ebe65554e16abe218e6f496fecbc0f0d85d258969c8dca5e867c56ed2dcb8cda507ae86ef46052109783e2 languageName: node linkType: hard -"@storybook/builder-vite@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/builder-vite@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/builder-vite@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/builder-vite@npm:0.0.0-pr-33653-sha-6047da63" dependencies: - "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/csf-plugin": "npm:0.0.0-pr-33653-sha-6047da63" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^0.0.0-pr-33653-sha-f37b6acd + storybook: ^0.0.0-pr-33653-sha-6047da63 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/914ba49e9a827686e49f82630af6f02c6cc43465d00107dac2d9eb12a5985595c62898b01ea63fe345a3a1d570035a0ed40de8f51fd40afd5dd0d568c5730d21 + checksum: 10c0/7ad3c2ffbac66da5fb6764e7a66c381702205303ea1b1ae278ffb0466168995cbdc04aff7f1b7c98c64d7eeca237e1d585bb64feb88f30423a98e2b114198773 languageName: node linkType: hard -"@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/csf-plugin@npm:0.0.0-pr-33653-sha-6047da63" dependencies: unplugin: "npm:^2.3.5" peerDependencies: esbuild: "*" rollup: "*" - storybook: ^0.0.0-pr-33653-sha-f37b6acd + storybook: ^0.0.0-pr-33653-sha-6047da63 vite: "*" webpack: "*" peerDependenciesMeta: @@ -2775,7 +2775,7 @@ __metadata: optional: true webpack: optional: true - checksum: 10c0/c22201322436fce43ac2d6da4479a6ddc70ce4077faf629099c276bcbec6d0ddb128effb7b9b303f8087ba550de6312833b8a2a4255820bf1ab8fc7c1fb93df3 + checksum: 10c0/ddf42f4b7d2572a087028fcd92e0e8e6a568b4d0f3f8888cef2a8f2f82561ca4f27efeda3378e964f6577404bcdc58ff82120b552955ac31f5c2fcfa0496ad58 languageName: node linkType: hard @@ -2796,25 +2796,25 @@ __metadata: languageName: node linkType: hard -"@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/react-dom-shim@npm:0.0.0-pr-33653-sha-6047da63" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^0.0.0-pr-33653-sha-f37b6acd - checksum: 10c0/f7ba4792c0d1b041e10986d28cae248eb9f53c5d8a8115e26c8e7011344e316973b31ddc56d19216f5e7c3cfaf840b0cd7c683b265c1bd37b8c83f28b1f7ca91 + storybook: ^0.0.0-pr-33653-sha-6047da63 + checksum: 10c0/5b20be6104b1f77399c5f935bea1a6863fddce5cbd103bf1a16c911b5baab9bc6c4e8740922f294fe7233247eb787a072aa83287793df962d1adf7f542cef28b languageName: node linkType: hard -"@storybook/react-vite@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/react-vite@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/react-vite@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/react-vite@npm:0.0.0-pr-33653-sha-6047da63" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.6.3" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:0.0.0-pr-33653-sha-f37b6acd" - "@storybook/react": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/builder-vite": "npm:0.0.0-pr-33653-sha-6047da63" + "@storybook/react": "npm:0.0.0-pr-33653-sha-6047da63" empathic: "npm:^2.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^8.0.0" @@ -2823,28 +2823,28 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^0.0.0-pr-33653-sha-f37b6acd + storybook: ^0.0.0-pr-33653-sha-6047da63 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/cf48a06d55cb39e7a1a34b637381c159719685a9bd0637d9b5138b7af0c3aab46c1df636cdbf2a183c7a592ba1a86f06822ed47bf36a5ca897d7894fa49ecf4a + checksum: 10c0/3cac23782047c966a0b9f41f72b3ccc19691a5eb8182863bd1c6a804f16ed5c7def06b0c1549ab85f074025a9723b6d92d88cdf6555e2fe66e90c7d36f32ffd9 languageName: node linkType: hard -"@storybook/react@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "@storybook/react@npm:0.0.0-pr-33653-sha-f37b6acd" +"@storybook/react@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "@storybook/react@npm:0.0.0-pr-33653-sha-6047da63" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-f37b6acd" + "@storybook/react-dom-shim": "npm:0.0.0-pr-33653-sha-6047da63" react-docgen: "npm:^8.0.2" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - storybook: ^0.0.0-pr-33653-sha-f37b6acd + storybook: ^0.0.0-pr-33653-sha-6047da63 typescript: ">= 4.9.x" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d85e813b6543481cd3ccac529c314609e848b44a107564d0a6631cc58fba4510a8950b5d67a366122a575f9d2dfd21fcbbaa49edb203b2a45b9dddfe39a568c9 + checksum: 10c0/0bbba8f7c09b43f8cb8fa23f85c85b921b2ffc55f04277494399f38640ccf7becc65147dc3db2a0253329c3221a53f2d763a62c7169af395f39289e0bd35db3f languageName: node linkType: hard @@ -5405,15 +5405,15 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-f37b6acd" +"eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "eslint-plugin-storybook@npm:0.0.0-pr-33653-sha-6047da63" dependencies: "@typescript-eslint/utils": "npm:^8.48.0" peerDependencies: eslint: ">=8" - storybook: ^0.0.0-pr-33653-sha-f37b6acd - checksum: 10c0/b851e44033965199fb2a90e6979bc60f1a713737e4305e21815372fd8be7981bd9e9fda6adcac95f420f9d0458b1538c931b01e03dbea4532012c452d7e2c96d + storybook: ^0.0.0-pr-33653-sha-6047da63 + checksum: 10c0/8a88972653e9cc88388d647754c8abe399014e11c61e3404000435fd6116c4e19234da9977536a01e00349c6c29f164d43ed8a03a395bb87f1225b2c08556116 languageName: node linkType: hard @@ -9549,9 +9549,9 @@ __metadata: languageName: node linkType: hard -"storybook@npm:0.0.0-pr-33653-sha-f37b6acd": - version: 0.0.0-pr-33653-sha-f37b6acd - resolution: "storybook@npm:0.0.0-pr-33653-sha-f37b6acd" +"storybook@npm:0.0.0-pr-33653-sha-6047da63": + version: 0.0.0-pr-33653-sha-6047da63 + resolution: "storybook@npm:0.0.0-pr-33653-sha-6047da63" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^2.0.1" @@ -9572,7 +9572,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10c0/fb17c77bbe930e9c929be5e77e67ef628c4a2b088559f6cd9e9dfaaf579f218636662254d105e9fa01fa0d6b6dad822d2fcfa8465f5591107563e76c4cd16ff5 + checksum: 10c0/c8e7002904512d7267c1888d359bd25ca4b5e18f2b840d502ab1638efecce8f03b0547ee6d853d1190c052cb0c9e1834afc1a22dce39c931cb07851a16645c5b languageName: node linkType: hard From e54521491237f510156bf5f94563e7ea1cbe92ff Mon Sep 17 00:00:00 2001 From: Norbert de Langen Date: Wed, 4 Feb 2026 17:27:17 +0100 Subject: [PATCH 10/11] Add order property to SHARE_PROVIDER in manager.tsx --- src/manager.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/manager.tsx b/src/manager.tsx index 88549f75..c6925b11 100644 --- a/src/manager.tsx +++ b/src/manager.tsx @@ -34,6 +34,7 @@ addons.register(ADDON_ID, (api) => { addons.add(SHARE_PROVIDER_ID, { type: Addon_TypesEnum.experimental_SHARE_PROVIDER, title: 'Upload & share', + order: -1, render: () => , } satisfies Omit); From 70db362d8df6731f2f3cbe397b80138c65ca858a Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 4 Feb 2026 19:59:02 +0100 Subject: [PATCH 11/11] Render panel content in ShareProviderRender --- src/ShareProviderRender.stories.tsx | 30 +++- src/ShareProviderRender.tsx | 221 ++++++++++++++++++++-------- src/manager.tsx | 8 +- src/runChromaticBuild.ts | 4 +- src/types.ts | 3 + 5 files changed, 200 insertions(+), 66 deletions(-) diff --git a/src/ShareProviderRender.stories.tsx b/src/ShareProviderRender.stories.tsx index 0d046fdb..08f88e08 100644 --- a/src/ShareProviderRender.stories.tsx +++ b/src/ShareProviderRender.stories.tsx @@ -2,7 +2,13 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import { API } from 'storybook/manager-api'; import { fn, mocked } from 'storybook/test'; -import { IS_OFFLINE, LOCAL_BUILD_PROGRESS, PROJECT_INFO, TEST_PROVIDER_ID } from './constants'; +import { + GIT_INFO, + IS_OFFLINE, + LOCAL_BUILD_PROGRESS, + PROJECT_INFO, + TEST_PROVIDER_ID, +} from './constants'; import { ShareProviderRender } from './ShareProviderRender'; import { GraphQLClientProvider } from './utils/graphQLClient'; import { MockChannel } from './utils/MockChannel'; @@ -35,6 +41,7 @@ const meta = { exchangeParameters: null, } as AuthValue, [PROJECT_INFO]: undefined, + [GIT_INFO]: undefined, [LOCAL_BUILD_PROGRESS]: undefined, [IS_OFFLINE]: false, }, @@ -52,6 +59,7 @@ const meta = { }, [LOCAL_BUILD_PROGRESS]: { type: 'object', target: 'shared-state' }, [PROJECT_INFO]: { type: 'object', target: 'shared-state' }, + [GIT_INFO]: { type: 'object', target: 'shared-state' }, [IS_OFFLINE]: { type: 'boolean', target: 'shared-state' }, getAddonState: { type: 'function', target: 'manager-api' }, setAddonState: { type: 'function', target: 'manager-api' }, @@ -95,7 +103,7 @@ const meta = { export default meta; type Story = StoryObj; -export const Setup: Story = {}; +export const Welcome: Story = {}; export const Login: Story = { args: { @@ -103,13 +111,29 @@ export const Login: Story = { }, }; -export const Ready: Story = { +export const SetupGit: Story = { args: { ...Login.args, auth: { token: 'test-token' }, }, }; +export const Ready: Story = { + args: { + ...SetupGit.args, + [GIT_INFO]: { + slug: 'test-slug', + branch: 'test-branch', + commit: 'test-commit', + committedAt: 1717334400, + uncommittedHash: '', + userEmail: '', + userEmailHash: '', + repositoryRootDir: 'root', + }, + }, +}; + export const Starting: Story = { args: { ...Ready.args, diff --git a/src/ShareProviderRender.tsx b/src/ShareProviderRender.tsx index f0d790d0..bfa29885 100644 --- a/src/ShareProviderRender.tsx +++ b/src/ShareProviderRender.tsx @@ -1,27 +1,49 @@ -import { FailedIcon } from '@storybook/icons'; import pluralize from 'pluralize'; -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { Link } from 'storybook/internal/components'; -import { Button } from 'storybook/internal/components'; -import { API, useStorybookApi, useStorybookState } from 'storybook/manager-api'; -import { color, styled } from 'storybook/theming'; +import React, { useCallback, useRef, useState } from 'react'; +import { Button, Link } from 'storybook/internal/components'; +import type { API } from 'storybook/manager-api'; +import { experimental_getStatusStore, useStorybookState } from 'storybook/manager-api'; +import { styled } from 'storybook/theming'; import { BUILD_STEP_CONFIG } from './buildSteps'; +import { Spinner } from './components/design-system'; import { ADDON_ID, CONFIG_INFO, + GIT_INFO, GIT_INFO_ERROR, IS_OFFLINE, LOCAL_BUILD_PROGRESS, + REMOVE_ADDON, + TELEMETRY, TEST_PROVIDER_ID, } from './constants'; -import { ConfigInfoPayload, LocalBuildProgress } from './types'; -import { TelemetryContext } from './utils/TelemetryContext'; +import { Authentication } from './screens/Authentication/Authentication'; +import { GitError } from './screens/Errors/GitError'; +import { LinkedProject } from './screens/LinkProject/LinkedProject'; +import { LinkingProjectFailed } from './screens/LinkProject/LinkingProjectFailed'; +import { LinkProject } from './screens/LinkProject/LinkProject'; +import { NoDevServer } from './screens/NoDevServer/NoDevServer'; +import { NoNetwork } from './screens/NoNetwork/NoNetwork'; +import { UninstallProvider } from './screens/Uninstalled/UninstallContext'; +import { Uninstalled } from './screens/Uninstalled/Uninstalled'; +import { ControlsProvider } from './screens/VisualTests/ControlsContext'; +import { RunBuildProvider } from './screens/VisualTests/RunBuildContext'; +import { + ConfigInfoPayload, + GitInfoPayload, + LocalBuildProgress, + UpdateStatusFunction, +} from './types'; +import { createClient, GraphQLClientProvider } from './utils/graphQLClient'; +import { TelemetryProvider } from './utils/TelemetryContext'; import { useAuth } from './utils/useAuth'; import { useBuildEvents } from './utils/useBuildEvents'; +import { useChannelFetch } from './utils/useChannelFetch'; import { useProjectId } from './utils/useProjectId'; +import { useSessionState } from './utils/useSessionState'; import { useSharedState } from './utils/useSharedState'; -import { getTestProviderStore, useTestProviderStore } from './utils/useTestProviderStore'; +import { useTestProviderStore } from './utils/useTestProviderStore'; const Container = styled.div(() => ({ display: 'flex', @@ -49,16 +71,26 @@ const Description = styled.div(({ theme }) => ({ color: theme.textMutedColor, })); +const statusStore = experimental_getStatusStore(ADDON_ID); + export const ShareProviderRender = ({ api }: { api: API }) => { const { addNotification, getStoryHrefs } = api; const { storyId } = useStorybookState(); - const trackEvent = useContext(TelemetryContext); - const { projectId } = useProjectId(); + const { + loading: projectInfoLoading, + projectId, + configFile, + updateProject, + projectUpdatingFailed, + projectIdUpdated, + clearProjectIdUpdated, + } = useProjectId(); + const [auth] = useAuth(); const isLoggedIn = !!auth.token; - const [isOffline, setOffline] = useSharedState(IS_OFFLINE); + const [isOffline] = useSharedState(IS_OFFLINE); const [localBuildProgress] = useSharedState(LOCAL_BUILD_PROGRESS); const [configInfo] = useSharedState(CONFIG_INFO); @@ -74,7 +106,7 @@ export const ShareProviderRender = ({ api }: { api: API }) => { (state) => state[TEST_PROVIDER_ID] ?? 'test-provider-state:pending' ); - const { startBuild, stopBuild } = useBuildEvents({ + const { isRunning, startBuild, stopBuild } = useBuildEvents({ localBuildProgress, accessToken: auth.token, }); @@ -94,55 +126,39 @@ export const ShareProviderRender = ({ api }: { api: API }) => { } }, [isRunnable, startBuild]); - useEffect( - () => getTestProviderStore(TEST_PROVIDER_ID).onRunAll(startBuildIfPossible), - [startBuildIfPossible] - ); + // const clickNotification = useCallback(({ onDismiss }: { onDismiss: () => void }) => { + // onDismiss(); + // }, []); - const clickNotification = useCallback(({ onDismiss }: { onDismiss: () => void }) => { - onDismiss(); - }, []); - - useEffect(() => { - const offline = () => setOffline(true); - const online = () => setOffline(false); - window.addEventListener('offline', offline); - window.addEventListener('online', online); - return () => { - window.removeEventListener('offline', offline); - window.removeEventListener('online', online); - }; - }, [setOffline]); - - useEffect(() => { - if (localBuildProgress?.currentStep === lastStep.current) return; - lastStep.current = localBuildProgress?.currentStep; - - if (localBuildProgress?.currentStep === 'error') { - addNotification({ - id: `${ADDON_ID}/build-error/${Date.now()}`, - content: { - headline: 'Build error', - subHeadline: 'Check the Storybook process on the command line for more details.', - }, - icon: , - onClick: clickNotification, - }); - } + // useEffect(() => { + // if (localBuildProgress?.currentStep === lastStep.current) return; + // lastStep.current = localBuildProgress?.currentStep; - if (localBuildProgress?.currentStep === 'limited') { - addNotification({ - id: `${ADDON_ID}/build-limited/${Date.now()}`, - content: { - headline: 'Build limited', - subHeadline: - 'Your account has insufficient snapshots remaining to run this build. Visit your billing page to find out more.', - }, - icon: , - onClick: clickNotification, - }); - } - }, [addNotification, clickNotification, localBuildProgress?.currentStep]); + // if (localBuildProgress?.currentStep === 'error') { + // addNotification({ + // id: `${ADDON_ID}/build-error/${Date.now()}`, + // content: { + // headline: 'Build error', + // subHeadline: 'Check the Storybook process on the command line for more details.', + // }, + // icon: , + // onClick: clickNotification, + // }); + // } + + // if (localBuildProgress?.currentStep === 'limited') { + // addNotification({ + // id: `${ADDON_ID}/build-limited/${Date.now()}`, + // content: { + // headline: 'Build limited', + // subHeadline: + // 'Your account has insufficient snapshots remaining to run this build. Visit your billing page to find out more.', + // }, + // icon: , + // onClick: clickNotification, + // }); + // } + // }, [addNotification, clickNotification, localBuildProgress?.currentStep]); const clickWarning = useCallback(() => {}, []); @@ -180,6 +196,91 @@ export const ShareProviderRender = ({ api }: { api: API }) => { (globalThis as any).STORYBOOK_NETWORK_ADDRESS = networkAddress; }; + const [gitInfo] = useSharedState(GIT_INFO); + const { emit } = api; + + // If the user creates a project in a dialog (either during login or later, it get set here) + const [createdProjectId, setCreatedProjectId] = useSessionState('createdProjectId'); + const [addonUninstalled, setAddonUninstalled] = useSharedState(REMOVE_ADDON); + + const trackEvent = useCallback((data: any) => emit(TELEMETRY, data), [emit]); + + const channelFetch = useChannelFetch(); + const fetch = globalThis.LOGLEVEL === 'debug' ? globalThis.fetch : channelFetch; + const withProviders = (children: React.ReactNode) => ( + + + + + +
{children}
+
+
+
+
+
+ ); + + if (addonUninstalled) { + return withProviders(null); + } + + if (globalThis.CONFIG_TYPE !== 'DEVELOPMENT') { + return withProviders(); + } + + if (isOffline) { + return withProviders(); + } + + // Render the Authentication flow if the user is not signed in. + if (!auth.token) { + return withProviders( + + ); + } + + if (gitInfoError || !gitInfo) { + return withProviders(); + } + + // Momentarily wait on addonState (should be very fast) + if (projectInfoLoading) { + return ; + } + + if (!projectId) { + return withProviders( + + ); + } + + if (projectUpdatingFailed) { + // These should always be set when we get this error + if (!configFile) throw new Error(`Missing config file after configuration failure`); + return withProviders(); + } + + if (projectIdUpdated) { + // This should always be set when we succeed + if (!configFile) throw new Error(`Missing config file after configuration success`); + + return withProviders( + + ); + } + return ( diff --git a/src/manager.tsx b/src/manager.tsx index c6925b11..2d1ffbe7 100644 --- a/src/manager.tsx +++ b/src/manager.tsx @@ -33,9 +33,13 @@ addons.register(ADDON_ID, (api) => { addons.add(SHARE_PROVIDER_ID, { type: Addon_TypesEnum.experimental_SHARE_PROVIDER, - title: 'Upload & share', + title: 'Share', order: -1, - render: () => , + render: () => ( +
+ +
+ ), } satisfies Omit); addons.add(TEST_PROVIDER_ID, { diff --git a/src/runChromaticBuild.ts b/src/runChromaticBuild.ts index 175c4cb9..0ab8bcac 100644 --- a/src/runChromaticBuild.ts +++ b/src/runChromaticBuild.ts @@ -120,6 +120,7 @@ export const onStartOrProgress = buildProgressPercentage: Math.min(newPercentage, endPercentage), currentStep: ctx.task, stepProgress, + storybookUrl: ctx.build?.storybookUrl, }; }; @@ -147,6 +148,7 @@ export const onCompleteOrError = buildProgressPercentage, stepProgress, previousBuildProgress: stepProgress, + storybookUrl: ctx.build?.storybookUrl, }; if (error) { @@ -198,7 +200,7 @@ export const runChromaticBuild = async ( // Set initial progress state. JSON.parse avoids mutating the constant. localBuildProgress.value = JSON.parse(INITIAL_BUILD_PAYLOAD_JSON) as LocalBuildProgress; - localBuildProgress.value.isPublishOnly = chromaticOptions.isPublishOnly; + localBuildProgress.value.isPublishOnly = publishOnly; // Timeout is defined here so it's shared between all handlers let timeout: ReturnType | undefined; diff --git a/src/types.ts b/src/types.ts index ef2d83f2..063cec9b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -93,4 +93,7 @@ export type LocalBuildProgress = { /** Progress tracking data from the previous build (if any) */ previousBuildProgress?: Record; + + /** The URL of the published Storybook instance */ + storybookUrl?: string; };