diff --git a/packages/bruno-app/src/components/OpenAPISyncTab/ConnectSpecForm/index.js b/packages/bruno-app/src/components/OpenAPISyncTab/ConnectSpecForm/index.js
index 19e29d54d..5069272a5 100644
--- a/packages/bruno-app/src/components/OpenAPISyncTab/ConnectSpecForm/index.js
+++ b/packages/bruno-app/src/components/OpenAPISyncTab/ConnectSpecForm/index.js
@@ -124,6 +124,17 @@ const ConnectSpecForm = ({ sourceUrl, setSourceUrl, isLoading, error, setError,
))}
+
+
+ OpenAPI Sync is in Beta — we'd love to hear your feedback and suggestions.{' '}
+
+
);
};
diff --git a/packages/bruno-app/src/components/OpenAPISyncTab/StyledWrapper.js b/packages/bruno-app/src/components/OpenAPISyncTab/StyledWrapper.js
index 122a6bdc2..51ff3c57e 100644
--- a/packages/bruno-app/src/components/OpenAPISyncTab/StyledWrapper.js
+++ b/packages/bruno-app/src/components/OpenAPISyncTab/StyledWrapper.js
@@ -2278,6 +2278,26 @@ const StyledWrapper = styled.div`
gap: 0.5rem;
flex-shrink: 0;
}
+
+ .beta-feedback-inline {
+ margin-top: 2rem;
+ font-size: ${(props) => props.theme.font.size.xs};
+ color: ${(props) => props.theme.colors.text.muted};
+
+ .beta-feedback-link {
+ background: none;
+ border: none;
+ padding: 0;
+ color: ${(props) => props.theme.status.info.text};
+ cursor: pointer;
+ font-size: inherit;
+ text-decoration: underline;
+
+ &:hover {
+ opacity: 0.8;
+ }
+ }
+ }
`;
export default StyledWrapper;
diff --git a/packages/bruno-app/src/components/OpenAPISyncTab/index.js b/packages/bruno-app/src/components/OpenAPISyncTab/index.js
index 58baae376..91335c23c 100644
--- a/packages/bruno-app/src/components/OpenAPISyncTab/index.js
+++ b/packages/bruno-app/src/components/OpenAPISyncTab/index.js
@@ -134,6 +134,16 @@ const OpenAPISyncTab = ({ collection }) => {
fileNotFound={fileNotFound}
onOpenSettings={() => setShowSettingsModal(true)}
/>
+
+ OpenAPI Sync is in Beta — we'd love to hear your feedback and suggestions.{' '}
+
+
)}
@@ -195,6 +205,7 @@ const OpenAPISyncTab = ({ collection }) => {
)}
>
)}
+
{showSettingsModal && (
diff --git a/packages/bruno-app/src/components/Preferences/Beta/index.js b/packages/bruno-app/src/components/Preferences/Beta/index.js
index 3da191500..cd1d68fb7 100644
--- a/packages/bruno-app/src/components/Preferences/Beta/index.js
+++ b/packages/bruno-app/src/components/Preferences/Beta/index.js
@@ -8,17 +8,19 @@ import debounce from 'lodash/debounce';
import toast from 'react-hot-toast';
import { IconFlask } from '@tabler/icons';
import get from 'lodash/get';
+import { BETA_FEATURES as BETA_FEATURE_IDS } from 'utils/beta-features';
/**
- * Add beta features here.
- * Example:
- * {
- * id: 'nodevm',
- * label: 'Node VM Runtime',
- * description: 'Enable Node VM runtime for JavaScript execution in Developer Mode'
- * }
+ * UI metadata for beta features rendered in Preferences.
+ * IDs must match keys from utils/beta-features.js BETA_FEATURES.
*/
-const BETA_FEATURES = [];
+const BETA_FEATURES = [
+ {
+ id: BETA_FEATURE_IDS.OPENAPI_SYNC,
+ label: 'OpenAPI Sync',
+ description: 'Synchronize your Bruno collection with an OpenAPI specification. Detect drift, review changes, and sync with a single click.'
+ }
+];
const Beta = ({ close }) => {
const preferences = useSelector((state) => state.app.preferences);
@@ -45,6 +47,7 @@ const Beta = ({ close }) => {
const betaSchema = generateValidationSchema();
const formik = useFormik({
+ enableReinitialize: true,
initialValues: generateInitialValues(),
validationSchema: betaSchema,
onSubmit: async (values) => {
@@ -61,7 +64,10 @@ const Beta = ({ close }) => {
dispatch(
savePreferences({
...preferences,
- beta: newBetaPreferences
+ beta: {
+ ...preferences.beta,
+ ...newBetaPreferences
+ }
})
)
.catch((err) => console.log(err) && toast.error('Failed to update beta preferences'));
diff --git a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
index dc93ae17d..693a30bbf 100644
--- a/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
+++ b/packages/bruno-app/src/components/RequestTabs/CollectionHeader/index.js
@@ -32,6 +32,8 @@ import { getRevealInFolderLabel } from 'utils/common/platform';
import classNames from 'classnames';
import StyledWrapper from './StyledWrapper';
import { useTheme } from 'providers/Theme';
+import { useBetaFeature, BETA_FEATURES } from 'utils/beta-features';
+import StatusBadge from 'ui/StatusBadge/index';
const CollectionHeader = ({ collection, isScratchCollection }) => {
const dispatch = useDispatch();
@@ -42,6 +44,8 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
// Get the current active workspace
const currentWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
+ const gitRootPath = collection?.git?.gitRootPath;
+ const isOpenAPISyncEnabled = useBetaFeature(BETA_FEATURES.OPENAPI_SYNC);
// Workspace rename state
const [isRenamingWorkspace, setIsRenamingWorkspace] = useState(false);
@@ -201,8 +205,8 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
// Build overflow menu items for the "..." dropdown
const overflowMenuItems = [
{ id: 'variables', label: 'Variables', leftSection: IconEye, onClick: viewVariables },
- ...(!hasOpenApiSyncConfigured
- ? [{ id: 'openapi-sync', label: 'OpenAPI', leftSection: OpenAPISyncIcon, onClick: viewOpenApiSync }]
+ ...(isOpenAPISyncEnabled && !hasOpenApiSyncConfigured
+ ? [{ id: 'openapi-sync', label: 'OpenAPI', leftSection: OpenAPISyncIcon, rightSection: Beta, onClick: viewOpenApiSync }]
: []),
{ id: 'collection-settings', label: 'Collection Settings', leftSection: IconSettings, onClick: viewCollectionSettings }
];
@@ -461,8 +465,8 @@ const CollectionHeader = ({ collection, isScratchCollection }) => {
{/* Right side: Actions (only for regular collections) */}
{!isScratchCollection && (
- {/* OpenAPI Sync - standalone only when configured */}
- {hasOpenApiSyncConfigured && (
+ {/* OpenAPI Sync - standalone only when configured and beta enabled */}
+ {isOpenAPISyncEnabled && hasOpenApiSyncConfigured && (
{
const getTabInfo = (type, tabName) => {
@@ -90,7 +91,8 @@ const SpecialTab = ({ handleCloseClick, type, tabName, handleDoubleClick, hasDra
return (
<>
- OpenAPI
+ OpenAPI
+ Beta
>
);
}
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
index 3a58b05d3..fbe8dea27 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
@@ -48,6 +48,8 @@ import { getRevealInFolderLabel } from 'utils/common/platform';
import { openDevtoolsAndSwitchToTerminal } from 'utils/terminal';
import ActionIcon from 'ui/ActionIcon';
import MenuDropdown from 'ui/MenuDropdown';
+import StatusBadge from 'ui/StatusBadge';
+import { useBetaFeature, BETA_FEATURES } from 'utils/beta-features';
import { useSidebarAccordion } from 'components/Sidebar/SidebarAccordionContext';
import { createEmptyStateMenuItems } from 'utils/collections/emptyStateRequest';
@@ -56,6 +58,7 @@ import { createEmptyStateMenuItems } from 'utils/collections/emptyStateRequest';
const EMPTY_STATE_DELAY_MS = 300;
const Collection = ({ collection, searchText }) => {
+ const isOpenAPISyncEnabled = useBetaFeature(BETA_FEATURES.OPENAPI_SYNC);
const { dropdownContainerRef } = useSidebarAccordion();
const [showNewFolderModal, setShowNewFolderModal] = useState(false);
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
@@ -382,12 +385,13 @@ const Collection = ({ collection, searchText }) => {
setShowCloneCollectionModalOpen(true);
}
},
- {
+ ...(isOpenAPISyncEnabled ? [{
id: 'sync-openapi',
leftSection: OpenAPISyncIcon,
label: 'OpenAPI',
+ rightSection: Beta,
onClick: openOpenAPISyncTab
- },
+ }] : []),
...(hasCopiedItems
? [
{
diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js
index c0199d450..91bce94c9 100644
--- a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js
+++ b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js
@@ -13,6 +13,7 @@ import { processBrunoCollection } from 'utils/importers/bruno-collection';
import { processOpenCollection } from 'utils/importers/opencollection';
import { wsdlToBruno } from '@usebruno/converters';
import { toastError } from 'utils/common/error';
+import { useBetaFeature, BETA_FEATURES } from 'utils/beta-features';
import Modal from 'components/Modal';
import Help from 'components/Help';
import Dropdown from 'components/Dropdown';
@@ -101,13 +102,14 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, rawData, format, sour
const dispatch = useDispatch();
const [groupingType, setGroupingType] = useState('tags');
const [collectionFormat, setCollectionFormat] = useState(DEFAULT_COLLECTION_FORMAT);
- const [enableCheckForSpecUpdates, setEnableCheckForSpecUpdates] = useState(true);
+ const isOpenAPISyncEnabled = useBetaFeature(BETA_FEATURES.OPENAPI_SYNC);
+ const [enableCheckForSpecUpdates, setEnableCheckForSpecUpdates] = useState(isOpenAPISyncEnabled);
const dropdownTippyRef = useRef();
const isOpenApi = format === 'openapi';
const isZipImport = format === 'bruno-zip';
const isOpenApiFromUrl = isOpenApi && !!sourceUrl && !filePath;
const isOpenApiFromFile = isOpenApi && !!filePath && !sourceUrl;
- const showCheckForSpecUpdatesOption = isOpenApiFromUrl || isOpenApiFromFile;
+ const showCheckForSpecUpdatesOption = isOpenAPISyncEnabled && (isOpenApiFromUrl || isOpenApiFromFile);
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const preferences = useSelector((state) => state.app.preferences);
diff --git a/packages/bruno-app/src/providers/App/useOpenAPISyncPolling.js b/packages/bruno-app/src/providers/App/useOpenAPISyncPolling.js
index aa5eb9027..21eba91f9 100644
--- a/packages/bruno-app/src/providers/App/useOpenAPISyncPolling.js
+++ b/packages/bruno-app/src/providers/App/useOpenAPISyncPolling.js
@@ -2,14 +2,16 @@ import { useEffect, useMemo, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { checkActiveWorkspaceCollectionsForUpdates } from 'providers/ReduxStore/slices/openapi-sync';
import { normalizePath } from 'utils/common/path';
+import { useBetaFeature, BETA_FEATURES } from 'utils/beta-features';
const POLL_INTERVAL = 5 * 60 * 1000; // 5 minutes
const useOpenAPISyncPolling = () => {
const dispatch = useDispatch();
- // Global toggle for pausing all OpenAPI sync polling (defaults to true, not yet wired to any UI)
- const pollingEnabled = useSelector((state) => state.openapiSync?.pollingEnabled ?? true);
+ const isOpenAPISyncEnabled = useBetaFeature(BETA_FEATURES.OPENAPI_SYNC);
+ // Global toggle for pausing all OpenAPI sync polling
+ const pollingEnabled = useSelector((state) => state.openapiSync?.pollingEnabled ?? true) && isOpenAPISyncEnabled;
const collections = useSelector((state) => state.collections?.collections || []);
const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces);
const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid);
diff --git a/packages/bruno-app/src/utils/beta-features.js b/packages/bruno-app/src/utils/beta-features.js
index 172e08499..84f9eb855 100644
--- a/packages/bruno-app/src/utils/beta-features.js
+++ b/packages/bruno-app/src/utils/beta-features.js
@@ -5,7 +5,8 @@ import { useSelector } from 'react-redux';
* Contains all available beta feature keys
*/
export const BETA_FEATURES = Object.freeze({
- NODE_VM: 'nodevm'
+ NODE_VM: 'nodevm',
+ OPENAPI_SYNC: 'openapi-sync'
});
/**
diff --git a/packages/bruno-electron/src/store/preferences.js b/packages/bruno-electron/src/store/preferences.js
index 48ed9db2a..ceb8ba5fe 100644
--- a/packages/bruno-electron/src/store/preferences.js
+++ b/packages/bruno-electron/src/store/preferences.js
@@ -45,7 +45,9 @@ const defaultPreferences = {
layout: {
responsePaneOrientation: 'horizontal'
},
- beta: {},
+ beta: {
+ 'openapi-sync': false
+ },
onboarding: {
hasLaunchedBefore: false,
hasSeenWelcomeModal: true
@@ -154,6 +156,7 @@ const preferencesSchema = Yup.object().shape({
responsePaneOrientation: Yup.string().oneOf(['horizontal', 'vertical'])
}),
beta: Yup.object({
+ 'openapi-sync': Yup.boolean()
}),
onboarding: Yup.object({
hasLaunchedBefore: Yup.boolean(),