fix: include overview docs in html documentation export (#7774)#7790
fix: include overview docs in html documentation export (#7774)#7790eeshm wants to merge 1 commit intousebruno:mainfrom
Conversation
WalkthroughThe pull request fixes collection-level documentation export by introducing a new utility to auto-generate an Overview folder from collection docs, preferring draft data when available, and wiring this resolution into the HTML documentation export pipeline. Changes
Sequence DiagramsequenceDiagram
participant GD as GenerateDocumentation
participant TC as transformCollectionToSaveToExportAsFile
participant BC as brunoToOpenCollection
participant RH as resolveCollectionForHtmlDocumentation
participant YAML as jsyaml.dump
GD->>TC: transformedCollection
TC->>TC: Extract from draft.root/draft.docs<br/>(fallback to saved root/docs)
TC-->>GD: transformed collection
GD->>BC: transformed collection
BC-->>GD: openCollection
GD->>RH: openCollection with docs
RH->>RH: Check for existing Overview
RH->>RH: Create Overview folder from docs
RH->>RH: Clear openCollection.docs
RH-->>GD: resolved openCollection
GD->>YAML: resolved openCollection
YAML-->>GD: YAML serialized content
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes HTML documentation export so Overview tab markdown (especially at the collection/folder level) is included in generated docs.html, including unsaved edits that exist only in draft state.
Changes:
- Update export transformation to prefer draft state over saved state for collection and folder roots.
- Add an HTML-docs-specific normalization step that converts collection-level docs into a top-level
Overviewfolder item (while avoiding duplicates / empty docs). - Wire the normalization into the
GenerateDocumentationflow before YAML is embedded intodocs.html, and add unit tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| packages/bruno-app/src/utils/tests/collections/examples-export-import.spec.js | Adds tests for draft-preferred export behavior and resolveCollectionForHtmlDocumentation normalization. |
| packages/bruno-app/src/utils/exporters/html-documentation.js | Introduces resolveCollectionForHtmlDocumentation to move collection-level docs into an Overview folder item for HTML export. |
| packages/bruno-app/src/utils/collections/index.js | Changes export transform to prefer draft roots for collection/folder docs (and omit empty draft docs). |
| packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.js | Applies the normalization before dumping YAML for docs.html. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/bruno-app/src/utils/exporters/html-documentation.js (1)
20-22: Case sensitivity on folder name match.
item?.info?.name !== OVERVIEW_FOLDER_NAMEis strict case-sensitive, so a user-created folder namedovervieworOVERVIEWwill be treated as unrelated and you'll end up with both folders in the sidebar. If that's acceptable (since this helper auto-generates the folder for export only), leave it — otherwise a.toLowerCase()compare is cheap insurance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/bruno-app/src/utils/exporters/html-documentation.js` around lines 20 - 22, The current check inside the items.some callback uses a case-sensitive comparison (item?.info?.name !== OVERVIEW_FOLDER_NAME), which will miss user folders named with different casing; update the comparison in the callback used by items.some to compare names case-insensitively (e.g., normalize both item?.info?.name and OVERVIEW_FOLDER_NAME with toLowerCase() or a locale-insensitive compare) so the helper reliably detects the overview folder; ensure you guard null/undefined before calling toLowerCase() on item?.info?.name.packages/bruno-app/src/utils/tests/collections/examples-export-import.spec.js (1)
394-416: Consider an additional case: existingOverviewfolder with empty docs.
hasTopLevelOverviewFolderWithDocsonly matches folders whose docs have non-empty content, so an existing emptyOverviewfolder would cause a second one to be prepended. Worth a test to lock in whatever behavior you want here (dedupe by name, or allow the duplicate).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/bruno-app/src/utils/tests/collections/examples-export-import.spec.js` around lines 394 - 416, Add a test covering an existing top-level "Overview" folder whose docs are empty: create a collection like openCollection but with items[0].info.name === 'Overview' and items[0].docs.content === '' (or null) and call resolveCollectionForHtmlDocumentation; assert either that no duplicate Overview is added (expect result.items toHaveLength(1) and result toBe(openCollection)) if you want dedupe-by-name, or assert a second Overview is prepended (expect length 2) if you allow duplicates; to enforce dedupe-by-name instead of docs-content matching, update hasTopLevelOverviewFolderWithDocs to check for a folder with info.name === 'Overview' regardless of docs.content and use that in resolveCollectionForHtmlDocumentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/bruno-app/src/utils/exporters/html-documentation.js`:
- Around line 19-42: hasTopLevelOverviewFolderWithDocs only checks for non-empty
docs, causing a new Overview to be prepended when an empty top-level Overview
folder already exists; modify resolveCollectionForHtmlDocumentation to first
check for any top-level folder named OVERVIEW_FOLDER_NAME (use items =
Array.isArray(openCollection.items) ? openCollection.items : [] and find by
item?.info?.type === 'folder' && item?.info?.name === OVERVIEW_FOLDER_NAME), and
if found and its getDocsContent(item.docs).trim() is empty while
openCollection.docs has content, merge by setting that folder.docs =
openCollection.docs and return openCollection (avoid prepending); otherwise
preserve existing behavior (return openCollection when folder already has docs
or when no merge is needed).
---
Nitpick comments:
In `@packages/bruno-app/src/utils/exporters/html-documentation.js`:
- Around line 20-22: The current check inside the items.some callback uses a
case-sensitive comparison (item?.info?.name !== OVERVIEW_FOLDER_NAME), which
will miss user folders named with different casing; update the comparison in the
callback used by items.some to compare names case-insensitively (e.g., normalize
both item?.info?.name and OVERVIEW_FOLDER_NAME with toLowerCase() or a
locale-insensitive compare) so the helper reliably detects the overview folder;
ensure you guard null/undefined before calling toLowerCase() on
item?.info?.name.
In
`@packages/bruno-app/src/utils/tests/collections/examples-export-import.spec.js`:
- Around line 394-416: Add a test covering an existing top-level "Overview"
folder whose docs are empty: create a collection like openCollection but with
items[0].info.name === 'Overview' and items[0].docs.content === '' (or null) and
call resolveCollectionForHtmlDocumentation; assert either that no duplicate
Overview is added (expect result.items toHaveLength(1) and result
toBe(openCollection)) if you want dedupe-by-name, or assert a second Overview is
prepended (expect length 2) if you allow duplicates; to enforce dedupe-by-name
instead of docs-content matching, update hasTopLevelOverviewFolderWithDocs to
check for a folder with info.name === 'Overview' regardless of docs.content and
use that in resolveCollectionForHtmlDocumentation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8c362923-cbbe-44c5-a170-8c70741b41cd
📒 Files selected for processing (4)
packages/bruno-app/src/components/Sidebar/Collections/Collection/GenerateDocumentation/index.jspackages/bruno-app/src/utils/collections/index.jspackages/bruno-app/src/utils/exporters/html-documentation.jspackages/bruno-app/src/utils/tests/collections/examples-export-import.spec.js
| const hasTopLevelOverviewFolderWithDocs = (items = []) => { | ||
| return items.some((item) => { | ||
| if (item?.info?.type !== 'folder' || item?.info?.name !== OVERVIEW_FOLDER_NAME) { | ||
| return false; | ||
| } | ||
|
|
||
| return getDocsContent(item.docs).trim().length > 0; | ||
| }); | ||
| }; | ||
|
|
||
| export const resolveCollectionForHtmlDocumentation = (openCollection) => { | ||
| if (!openCollection || typeof openCollection !== 'object') { | ||
| return openCollection; | ||
| } | ||
|
|
||
| const docsContent = getDocsContent(openCollection.docs).trim(); | ||
| if (!docsContent.length) { | ||
| return openCollection; | ||
| } | ||
|
|
||
| const items = Array.isArray(openCollection.items) ? openCollection.items : []; | ||
| if (hasTopLevelOverviewFolderWithDocs(items)) { | ||
| return openCollection; | ||
| } |
There was a problem hiding this comment.
Edge case: an existing empty Overview folder yields two Overview entries.
hasTopLevelOverviewFolderWithDocs returns false when a top-level folder named Overview exists but has empty/whitespace docs. In that case this helper will still prepend a new Overview folder, leaving the collection with two sibling folders of the same name — which the docs sidebar will render as two separate "Overview" entries.
Two reasonable options:
- Merge: if a top-level
Overviewfolder exists, populate itsdocsinstead of prepending. - Skip-by-name: bail out whenever any top-level folder is named
Overview, regardless of docs content.
♻️ Example: merge into existing empty Overview
- const items = Array.isArray(openCollection.items) ? openCollection.items : [];
- if (hasTopLevelOverviewFolderWithDocs(items)) {
- return openCollection;
- }
-
- const overviewItem = {
- info: {
- name: OVERVIEW_FOLDER_NAME,
- type: 'folder'
- },
- docs: {
- content: docsContent,
- type: 'text/markdown'
- }
- };
-
- return {
- ...openCollection,
- docs: undefined,
- items: [overviewItem, ...items]
- };
+ const items = Array.isArray(openCollection.items) ? openCollection.items : [];
+ const existingIdx = items.findIndex(
+ (i) => i?.info?.type === 'folder' && i?.info?.name === OVERVIEW_FOLDER_NAME
+ );
+ if (existingIdx !== -1 && getDocsContent(items[existingIdx].docs).trim().length > 0) {
+ return openCollection;
+ }
+
+ const overviewDocs = { content: docsContent, type: 'text/markdown' };
+ const nextItems = [...items];
+ if (existingIdx !== -1) {
+ nextItems[existingIdx] = { ...nextItems[existingIdx], docs: overviewDocs };
+ } else {
+ nextItems.unshift({
+ info: { name: OVERVIEW_FOLDER_NAME, type: 'folder' },
+ docs: overviewDocs
+ });
+ }
+
+ return { ...openCollection, docs: undefined, items: nextItems };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const hasTopLevelOverviewFolderWithDocs = (items = []) => { | |
| return items.some((item) => { | |
| if (item?.info?.type !== 'folder' || item?.info?.name !== OVERVIEW_FOLDER_NAME) { | |
| return false; | |
| } | |
| return getDocsContent(item.docs).trim().length > 0; | |
| }); | |
| }; | |
| export const resolveCollectionForHtmlDocumentation = (openCollection) => { | |
| if (!openCollection || typeof openCollection !== 'object') { | |
| return openCollection; | |
| } | |
| const docsContent = getDocsContent(openCollection.docs).trim(); | |
| if (!docsContent.length) { | |
| return openCollection; | |
| } | |
| const items = Array.isArray(openCollection.items) ? openCollection.items : []; | |
| if (hasTopLevelOverviewFolderWithDocs(items)) { | |
| return openCollection; | |
| } | |
| const hasTopLevelOverviewFolderWithDocs = (items = []) => { | |
| return items.some((item) => { | |
| if (item?.info?.type !== 'folder' || item?.info?.name !== OVERVIEW_FOLDER_NAME) { | |
| return false; | |
| } | |
| return getDocsContent(item.docs).trim().length > 0; | |
| }); | |
| }; | |
| export const resolveCollectionForHtmlDocumentation = (openCollection) => { | |
| if (!openCollection || typeof openCollection !== 'object') { | |
| return openCollection; | |
| } | |
| const docsContent = getDocsContent(openCollection.docs).trim(); | |
| if (!docsContent.length) { | |
| return openCollection; | |
| } | |
| const items = Array.isArray(openCollection.items) ? openCollection.items : []; | |
| const existingIdx = items.findIndex( | |
| (i) => i?.info?.type === 'folder' && i?.info?.name === OVERVIEW_FOLDER_NAME | |
| ); | |
| if (existingIdx !== -1 && getDocsContent(items[existingIdx].docs).trim().length > 0) { | |
| return openCollection; | |
| } | |
| const overviewDocs = { content: docsContent, type: 'text/markdown' }; | |
| const nextItems = [...items]; | |
| if (existingIdx !== -1) { | |
| nextItems[existingIdx] = { ...nextItems[existingIdx], docs: overviewDocs }; | |
| } else { | |
| nextItems.unshift({ | |
| info: { name: OVERVIEW_FOLDER_NAME, type: 'folder' }, | |
| docs: overviewDocs | |
| }); | |
| } | |
| return { ...openCollection, docs: undefined, items: nextItems }; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/bruno-app/src/utils/exporters/html-documentation.js` around lines 19
- 42, hasTopLevelOverviewFolderWithDocs only checks for non-empty docs, causing
a new Overview to be prepended when an empty top-level Overview folder already
exists; modify resolveCollectionForHtmlDocumentation to first check for any
top-level folder named OVERVIEW_FOLDER_NAME (use items =
Array.isArray(openCollection.items) ? openCollection.items : [] and find by
item?.info?.type === 'folder' && item?.info?.name === OVERVIEW_FOLDER_NAME), and
if found and its getDocsContent(item.docs).trim() is empty while
openCollection.docs has content, merge by setting that folder.docs =
openCollection.docs and return openCollection (avoid prepending); otherwise
preserve existing behavior (return openCollection when folder already has docs
or when no merge is needed).
|
Thanks for the PR @eeshm ! At this stage, this is under consideration along with other requests. We’ll follow up once there’s a clearer decision or prioritization. |
|
Sure @helloanoop . thanks for the update. let me know if you need anything else from my side |
Description
fixes: #7774
This fixes an issue where content written in the Overview tab (especially at the collection level) was missing from generated
docs.html.Root cause
The HTML docs export had two gaps:
root) and could miss unsaved Overview edits indraft.openCollection.docs, but the docs UI rendering path reliably displays docs attached to items/folders. As a result, collection Overview content was not consistently visible in exported HTML.What changed
collection.draft.root || collection.rootfolder.draft || folder.rootOverviewfolder item with markdown docsOverviewfolder with docs already existsGenerateDocumentationbefore YAML is embedded intodocs.html.Behavior after fix
docs.htmlScreenshot:

Contribution Checklist:
Summary by CodeRabbit
Bug Fixes
Tests