From 945d436ceba845a4c838604313219fe8579eb481 Mon Sep 17 00:00:00 2001 From: lohit-bruno Date: Wed, 1 Apr 2026 20:40:33 +0530 Subject: [PATCH 1/4] feat: add Fetch in Bruno button with direct deep link Thread gitCollectionUrl prop from OpenCollection through Redux store to the Sidebar, where an inline SVG button links directly to the bruno:// protocol handler for git collection import. --- .../Docs/Sidebar/FetchInBrunoButton.tsx | 31 +++++++++++++++++++ .../src/components/Docs/Sidebar/Sidebar.tsx | 13 +++++++- .../OpenCollection/OpenCollection.tsx | 9 +++++- packages/oc-docs/src/dev.tsx | 1 + packages/oc-docs/src/standalone.ts | 4 ++- packages/oc-docs/src/store/slices/app.ts | 11 +++++-- 6 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 packages/oc-docs/src/components/Docs/Sidebar/FetchInBrunoButton.tsx diff --git a/packages/oc-docs/src/components/Docs/Sidebar/FetchInBrunoButton.tsx b/packages/oc-docs/src/components/Docs/Sidebar/FetchInBrunoButton.tsx new file mode 100644 index 0000000..28d8b35 --- /dev/null +++ b/packages/oc-docs/src/components/Docs/Sidebar/FetchInBrunoButton.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +const FetchInBrunoButton: React.FC = () => ( + + + + + + + + + + + + + + + + + + + + + + + + Fetch In Bruno + +); + +export default FetchInBrunoButton; diff --git a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx index 029a3e7..9d09f7b 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx @@ -5,8 +5,10 @@ import type { HttpRequest } from '@opencollection/types/requests/http'; import Method from '../Method/Method'; import OpenCollectionLogo from '../../../assets/opencollection-logo.svg'; import { SidebarContainer, SidebarItems, SidebarItem } from './StyledWrapper'; +import FetchInBrunoButton from './FetchInBrunoButton'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; import { toggleItem, selectItem, selectSelectedItemId, selectDocsCollection } from '../../../store/slices/docs'; +import { selectGitCollectionUrl } from '../../../store/slices/app'; import { getItemType, getItemName, getHttpMethod, isFolder, isHttpRequest } from '../../../utils/schemaHelpers'; export interface SidebarProps { @@ -16,6 +18,7 @@ const Sidebar: React.FC = () => { const dispatch = useAppDispatch(); const selectedItemId = useAppSelector(selectSelectedItemId); const collection = useAppSelector(selectDocsCollection); + const gitCollectionUrl = useAppSelector(selectGitCollectionUrl); const toggleFolder = (itemUuid: string) => { dispatch(toggleItem(itemUuid)); @@ -124,10 +127,18 @@ const Sidebar: React.FC = () => { {/* Collection name at top */}
-
+

{collection?.info?.name || 'API Collection'}

+ {gitCollectionUrl && ( + + + + )}
diff --git a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx index 241cd19..258420a 100644 --- a/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx +++ b/packages/oc-docs/src/components/OpenCollection/OpenCollection.tsx @@ -27,7 +27,8 @@ import { setCollectionLoading, setCollectionSucceeded, setCollectionFailed, - resetCollectionState + resetCollectionState, + setGitCollectionUrl } from '@slices/app'; import { createOpenCollectionStore, type AppStore } from '../../store/store'; @@ -162,10 +163,12 @@ const DesktopLayout: React.FC = ({ export interface OpenCollectionProps { collection: IOpenCollection | string | File; logo?: React.ReactNode; + gitCollectionUrl?: string; } const OpenCollectionContent: React.FC = ({ collection, + gitCollectionUrl, }) => { const dispatch = useAppDispatch(); const docsCollection = useAppSelector(selectDocsCollection); @@ -174,6 +177,10 @@ const OpenCollectionContent: React.FC = ({ const collectionError = useAppSelector(selectCollectionError); const selectedItemId = useAppSelector((state) => state.docs.selectedItemId); + useEffect(() => { + gitCollectionUrl && dispatch(setGitCollectionUrl(gitCollectionUrl)); + }, [gitCollectionUrl, dispatch]); + useEffect(() => { let isActive = true; diff --git a/packages/oc-docs/src/dev.tsx b/packages/oc-docs/src/dev.tsx index 3a06af2..0e7bf68 100644 --- a/packages/oc-docs/src/dev.tsx +++ b/packages/oc-docs/src/dev.tsx @@ -31,6 +31,7 @@ const DevApp: React.FC = () => {
diff --git a/packages/oc-docs/src/standalone.ts b/packages/oc-docs/src/standalone.ts index 0cb0a0b..02b4574 100644 --- a/packages/oc-docs/src/standalone.ts +++ b/packages/oc-docs/src/standalone.ts @@ -23,6 +23,7 @@ export interface OpenCollectionOptions { target: HTMLElement; opencollection: any; logo?: string; + gitCollectionUrl?: string; } export class OpenCollectionRenderer { @@ -96,7 +97,8 @@ export class OpenCollectionRenderer { this.root.render(React.createElement(OpenCollection, { collection, - logo: this.createLogoElement() + logo: this.createLogoElement(), + gitCollectionUrl: this.options.gitCollectionUrl })); } diff --git a/packages/oc-docs/src/store/slices/app.ts b/packages/oc-docs/src/store/slices/app.ts index cd36008..fbc2d04 100644 --- a/packages/oc-docs/src/store/slices/app.ts +++ b/packages/oc-docs/src/store/slices/app.ts @@ -6,11 +6,13 @@ export type CollectionStatus = 'idle' | 'loading' | 'succeeded' | 'failed'; export interface AppState { collectionStatus: CollectionStatus; collectionError: string | null; + gitCollectionUrl: string | null; } const initialState: AppState = { collectionStatus: 'idle', - collectionError: null + collectionError: null, + gitCollectionUrl: null }; const appSlice = createSlice({ @@ -32,6 +34,9 @@ const appSlice = createSlice({ resetCollectionState: (state: AppState) => { state.collectionStatus = 'idle'; state.collectionError = null; + }, + setGitCollectionUrl: (state: AppState, action: PayloadAction) => { + state.gitCollectionUrl = action.payload; } } }); @@ -40,11 +45,13 @@ export const { setCollectionLoading, setCollectionSucceeded, setCollectionFailed, - resetCollectionState + resetCollectionState, + setGitCollectionUrl } = appSlice.actions; export default appSlice.reducer; export const selectCollectionStatus = (state: RootState) => state.app.collectionStatus; export const selectCollectionError = (state: RootState) => state.app.collectionError; +export const selectGitCollectionUrl = (state: RootState) => state.app.gitCollectionUrl; From 3c812a8279fe8d39280262ace3c117f4122263d5 Mon Sep 17 00:00:00 2001 From: lohit-bruno Date: Wed, 22 Apr 2026 21:59:04 +0530 Subject: [PATCH 2/4] move collection name header to main content area Display collection name as a page header at the top of the docs content area with the Fetch in Bruno button beside it, instead of in the sidebar. Drop the redundant markdown H1 from the sample collection now that the name renders as its own header. --- packages/oc-docs/src/components/Docs/Docs.tsx | 25 +++++++++++++++++++ .../src/components/Docs/Sidebar/Sidebar.tsx | 20 --------------- packages/oc-docs/src/sampleCollection.ts | 2 -- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/packages/oc-docs/src/components/Docs/Docs.tsx b/packages/oc-docs/src/components/Docs/Docs.tsx index a1e602c..d254c41 100644 --- a/packages/oc-docs/src/components/Docs/Docs.tsx +++ b/packages/oc-docs/src/components/Docs/Docs.tsx @@ -3,10 +3,12 @@ import type { OpenCollection as OpenCollectionCollection } from '@opencollection import type { StructuredText } from '@opencollection/types/common/description'; import Sidebar from './Sidebar/Sidebar'; import Item from './Item/Item'; +import FetchInBrunoButton from './Sidebar/FetchInBrunoButton'; import { getItemId, generateSafeId } from '../../utils/itemUtils'; import { isFolder } from '../../utils/schemaHelpers'; import { useAppSelector, useAppDispatch } from '../../store/hooks'; import { selectSelectedItemId, selectItem } from '../../store/slices/docs'; +import { selectGitCollectionUrl } from '../../store/slices/app'; import { useMarkdownRenderer } from '../../hooks'; interface DocsProps { @@ -22,6 +24,7 @@ const Docs: React.FC = ({ }) => { const dispatch = useAppDispatch(); const selectedItemId = useAppSelector(selectSelectedItemId); + const gitCollectionUrl = useAppSelector(selectGitCollectionUrl); const md = useMarkdownRenderer(); const isInitialMount = useRef(true); @@ -112,6 +115,28 @@ const Docs: React.FC = ({ className="playground-content h-full overflow-y-auto flex-1" >
+ {docsCollection?.info?.name && ( +
+

+ {docsCollection.info.name} +

+ {gitCollectionUrl && ( + + + + )} +
+ )} + {/* Collection-level documentation/introduction */} {docsCollection?.docs && (
= () => { const dispatch = useAppDispatch(); const selectedItemId = useAppSelector(selectSelectedItemId); const collection = useAppSelector(selectDocsCollection); - const gitCollectionUrl = useAppSelector(selectGitCollectionUrl); const toggleFolder = (itemUuid: string) => { dispatch(toggleItem(itemUuid)); @@ -125,23 +122,6 @@ const Sidebar: React.FC = () => { return ( - {/* Collection name at top */} -
-
-

- {collection?.info?.name || 'API Collection'} -

- {gitCollectionUrl && ( - - - - )} -
-
- {collection?.items?.length && ( collection.items.map((item) => renderItem(item)) diff --git a/packages/oc-docs/src/sampleCollection.ts b/packages/oc-docs/src/sampleCollection.ts index 0aead7d..3c4f065 100644 --- a/packages/oc-docs/src/sampleCollection.ts +++ b/packages/oc-docs/src/sampleCollection.ts @@ -32,8 +32,6 @@ request: token: "{{bearer_auth_token}}" docs: content: | - # Bruno Testbench - This is a comprehensive API collection for testing **OpenCollection** features. ## Getting Started From 6fd303d1d2e612a6e78605982bc0bc4620d02a6c Mon Sep 17 00:00:00 2001 From: lohit-bruno Date: Wed, 22 Apr 2026 22:06:02 +0530 Subject: [PATCH 3/4] update e2e tests for relocated collection name header The H1 'Bruno Testbench' is no longer inside .collection-docs since the collection name now renders as a page-level header above it. Drop the stale assertion and add a new test verifying the name appears above the docs section. --- packages/oc-docs/e2e/collection-docs.spec.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/oc-docs/e2e/collection-docs.spec.ts b/packages/oc-docs/e2e/collection-docs.spec.ts index 7d858d1..be05d19 100644 --- a/packages/oc-docs/e2e/collection-docs.spec.ts +++ b/packages/oc-docs/e2e/collection-docs.spec.ts @@ -19,12 +19,20 @@ test.describe('Collection-level documentation', () => { test('renders markdown headings', async ({ page }) => { const docs = page.locator('.collection-docs'); - await expect(docs.getByRole('heading', { name: 'Bruno Testbench', level: 1 })).toBeVisible(); await expect(docs.getByRole('heading', { name: 'Getting Started', level: 2 })).toBeVisible(); await expect(docs.getByRole('heading', { name: 'Authentication', level: 2 })).toBeVisible(); await expect(docs.getByRole('heading', { name: 'Rate Limits', level: 2 })).toBeVisible(); }); + test('renders collection name as page header above docs', async ({ page }) => { + const heading = page.getByRole('heading', { name: 'Bruno Testbench', level: 1 }); + await expect(heading).toBeVisible(); + + const headingBox = await heading.boundingBox(); + const docsBox = await page.locator('.collection-docs').boundingBox(); + expect(headingBox!.y).toBeLessThan(docsBox!.y); + }); + test('renders markdown paragraphs with inline formatting', async ({ page }) => { const docs = page.locator('.collection-docs'); await expect(docs.getByText('comprehensive API collection for testing')).toBeVisible(); From fa922fc9687ccd72fb43d62f76c3cc58f1bab6d1 Mon Sep 17 00:00:00 2001 From: lohit-bruno Date: Thu, 23 Apr 2026 16:56:54 +0530 Subject: [PATCH 4/4] restore collection name heading in sidebar, scope e2e test accordingly --- packages/oc-docs/e2e/collection-docs.spec.ts | 4 +++- packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/oc-docs/e2e/collection-docs.spec.ts b/packages/oc-docs/e2e/collection-docs.spec.ts index be05d19..2dbabc1 100644 --- a/packages/oc-docs/e2e/collection-docs.spec.ts +++ b/packages/oc-docs/e2e/collection-docs.spec.ts @@ -25,7 +25,9 @@ test.describe('Collection-level documentation', () => { }); test('renders collection name as page header above docs', async ({ page }) => { - const heading = page.getByRole('heading', { name: 'Bruno Testbench', level: 1 }); + const heading = page + .locator('.playground-content') + .getByRole('heading', { name: 'Bruno Testbench', level: 1 }); await expect(heading).toBeVisible(); const headingBox = await heading.boundingBox(); diff --git a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx index dbe0697..be53f33 100644 --- a/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx +++ b/packages/oc-docs/src/components/Docs/Sidebar/Sidebar.tsx @@ -122,6 +122,15 @@ const Sidebar: React.FC = () => { return ( + {/* Collection name at top */} +
+
+

+ {collection?.info?.name || 'API Collection'} +

+
+
+ {collection?.items?.length && ( collection.items.map((item) => renderItem(item))