Skip to content

feat(app): introduce standalone app functionality#8338

Merged
naman-bruno merged 3 commits into
usebruno:mainfrom
naman-bruno:feat/app-collection
Jun 24, 2026
Merged

feat(app): introduce standalone app functionality#8338
naman-bruno merged 3 commits into
usebruno:mainfrom
naman-bruno:feat/app-collection

Conversation

@naman-bruno

@naman-bruno naman-bruno commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Description

JIRA

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • New Features
    • Added standalone “app” items to collections with “New App” creation, sidebar/tab support, and Code/Preview views using a webview context API (list/run requests, runtime variables)
    • Added an app empty-state for tabs without code and extended AI Assist for app-specific suggestions and script generation
  • Bug Fixes
    • Improved app-item persistence/export/import and deletion handling, and ensured app code edits are saved correctly
  • Tests
    • Added end-to-end Playwright coverage for collection apps and the in-webview ctx API

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 926a2fe6-a4f5-4d5b-a5f4-81885a3f33c9

📥 Commits

Reviewing files that changed from the base of the PR and between 179c4fa and 459b4b3.

📒 Files selected for processing (1)
  • tests/apps/collection-apps.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/apps/collection-apps.spec.ts

Walkthrough

Introduces a first-class 'app' item type in Bruno collections. A new CollectionApp component renders a Code/Preview webview with a host/guest RPC bridge (useAppWebview), exposing a window.ctx API for listing/running requests and reading variables. The feature spans file format support (BRU/YML), Redux actions, Electron IPC, sidebar/tab UI, AI-assisted code generation, and E2E tests.

Changes

Collection Apps Feature

Layer / File(s) Summary
Schema and type contracts for 'app' items
packages/bruno-schema-types/src/collection/item.ts, packages/bruno-schema/src/collections/index.js, packages/bruno-app/src/utils/collections/index.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
ItemType union and Yup schema gain the 'app' discriminator; transformRequestToSaveToFilesystem returns a minimal save object for app items; updateAppCode applies to standalone app items.
BRU and YML file format support
packages/bruno-filestore/src/formats/bru/index.ts, packages/bruno-filestore/src/formats/yml/items/parseApp.ts, packages/bruno-filestore/src/formats/yml/items/stringifyApp.ts, packages/bruno-filestore/src/formats/yml/parseItem.ts, packages/bruno-filestore/src/formats/yml/stringifyItem.ts
Early-return branches in parseBruRequest/stringifyBruRequest handle meta.type === 'app'; new parseApp/stringifyApp modules handle YML round-tripping; both parseItem and stringifyItem route case 'app' to the new handlers.
Redux newApp action, draft middleware, IPC deletion
packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js, packages/bruno-app/src/providers/ReduxStore/middlewares/draft/middleware.js, packages/bruno-electron/src/ipc/collection.js
newApp thunk creates an app item on disk via IPC with a starter template, seq assignment, and duplicate checks; updateAppCode is added to the draft middleware intercept list; renderer:delete-item gains an explicit 'app' file deletion path.
webview-bridge: transport contract and useAppWebview hook
packages/bruno-app/src/components/AppView/webview-bridge.js
New module exports SENTINEL, toJsArg, wrapHtml, toDataUrl, serializeTimeline, projectResponse, and useAppWebview—a hook managing dom-ready state, an outbox queue flushed via executeJavaScript, and inbound routing via filtered console-message events.
AppView refactored onto useAppWebview with EmptyAppState
packages/bruno-app/src/components/AppView/index.js, packages/bruno-app/src/components/AppView/EmptyAppState.js
AppView drops local webview state/event wiring in favor of useAppWebview; src is built from wrapHtml+toDataUrl; a pushToGuestRef stabilises the request bridge across re-renders; EmptyAppState is shown when code is absent.
CollectionApp component: bootstrap, RPC, and Code/Preview UI
packages/bruno-app/src/components/CollectionApp/index.js, packages/bruno-app/src/components/CollectionApp/StyledWrapper.js
New CollectionApp embeds COLLECTION_CTX_BOOTSTRAP (defining window.ctx, awaitReply RPC, __brunoReceive), implements handleGuestMessage for listRequests/runRequest/setRuntimeVariable, pushes state updates via effects, and renders a Code/Preview toggle with CodeEditor and <webview>.
AppCodeEditor and AIAssist integration
packages/bruno-app/src/components/RequestPane/AppCodeEditor/index.js, packages/bruno-app/src/components/AIAssist/index.js, packages/bruno-electron/src/ipc/ai/script-prompts.js
AppCodeEditor renders AIAssist instead of CodeEditor with computed requestContext; AIAssist gains app-request and app-collection script types with dedicated suggestions, titles, and preview labels; SCRIPT_PROMPTS adds detailed app generation instructions and updates buildScriptUserPrompt to select appropriate labels/fences for app types.
NewApp creation modal
packages/bruno-app/src/components/Sidebar/NewApp/index.js
Formik/Yup modal that validates and dispatches newApp with derived filename, shows toasts on success/error, and auto-focuses the input on mount.
Sidebar and tab routing for 'app' items
packages/bruno-app/src/components/RequestTabPanel/index.js, packages/bruno-app/src/components/RequestTabs/RequestTab/index.js, packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js, packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js, packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemIcon/index.js
RequestTabPanel short-circuits to CollectionApp for item.type === 'app'; RequestTab renders IconApps instead of method text; sidebar components gain New App menu entries, modal state, icon rendering, click handling, and item counting for app items.
E2E tests and utilities
tests/apps/collection-apps.spec.ts, tests/utils/page/actions.ts
Playwright suite covers app creation/sidebar/tab/toggle UI, ctx.listRequests, ctx.runRequest with body overrides, ctx.setRuntimeVariable persistence, and ctx.collection.name; createApp and selectAppView helpers added to test utilities.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(70, 130, 180, 0.5)
    note over User,Redux: Creating an App Item
    User->>Sidebar: Click "New App" menu
    Sidebar->>NewApp: Open modal
    NewApp->>Redux: dispatch newApp({ appName, filename, collectionUid })
    Redux->>Electron IPC: renderer:new-request (type: 'app')
    Electron IPC-->>Redux: item created on disk
    Redux->>Redux: enqueue OPEN_REQUEST task
  end
  rect rgba(60, 179, 113, 0.5)
    note over CollectionApp,Guest: Host/Guest RPC in Preview
    CollectionApp->>useAppWebview: mount with handleGuestMessage
    useAppWebview->>webview: dom-ready → flush outbox via executeJavaScript
    webview-->>Guest: __brunoReceive({ type: 'state', ... })
    Guest->>webview: console.log(SENTINEL + JSON { type: 'runRequest' })
    webview-->>useAppWebview: console-message event
    useAppWebview->>CollectionApp: handleGuestMessage({ type: 'runRequest' })
    CollectionApp->>Redux: dispatch sendNetworkRequest
    Redux-->>CollectionApp: response
    CollectionApp->>useAppWebview: pushToGuest({ type: 'reply', result })
    useAppWebview->>webview: executeJavaScript(__brunoReceive(...))
    webview-->>Guest: pending[replyId] resolves
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • usebruno/bruno#8294: Directly modifies AppView/index.js and the webview bootstrap/bridge behavior that this PR refactors onto useAppWebview.
  • usebruno/bruno#8220: Extends AIAssist integration by adding new scriptType entries (app-request, app-collection) with dedicated prompt templates and output rules.
  • usebruno/bruno#8246: Modifies packages/bruno-app/src/components/AIAssist/index.js to extend SUGGESTIONS/UI handling for new scriptType values, overlapping at the same code paths.

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno
  • sid-bruno
  • vijayh-bruno

🖥️ A new 'app' has joined the collection fray,
With ctx.runRequest it saves the day!
SENTINEL guards the console stream so bright,
wrapHtml stitches the guest's code right.
From BRU to YML, the parsers align—
Collection apps with AIAssist design! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing standalone app functionality as a new feature across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
packages/bruno-filestore/src/formats/yml/items/parseApp.ts (1)

14-35: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider validating ocApp.info presence before casting.

Line 15 falls back to an empty object when ocApp.info is missing, which means all nested fields will be undefined. While the code handles this with defaults (line 21 defaults name to 'App', line 22 defaults tags to []), this silently accepts malformed input.

If the AppFile contract requires info to be present, consider throwing an error early rather than silently recovering.

🛡️ Proposed validation guard
 const parseApp = (ocApp: AppFile): BrunoItem => {
-  const info = ocApp.info || ({} as AppFile['info']);
+  if (!ocApp.info) {
+    throw new Error('Invalid app item: missing info field');
+  }
+  const info = ocApp.info;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-filestore/src/formats/yml/items/parseApp.ts` around lines 14 -
35, The parseApp function silently accepts a missing ocApp.info field by
defaulting to an empty object, which can hide malformed input. Add a validation
check at the beginning of the parseApp function to ensure that ocApp.info is
present and defined; if it's missing and required by the AppFile contract, throw
a descriptive error early before proceeding with the rest of the function logic
and object creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bruno-app/src/components/AppView/EmptyAppState.js`:
- Line 29: The component inconsistently accesses theme properties by mixing two
different patterns: the Wrapper uses theme.colors.text.muted while the title
styling uses top-level theme.text. To resolve this inconsistency, update the
title color property in the styled component to access the text color through
the same theme.colors.text.* pattern used by the Wrapper, ensuring uniform theme
property access throughout the EmptyAppState component.

In `@packages/bruno-app/src/components/Sidebar/NewApp/index.js`:
- Around line 63-78: The form and input elements in the NewApp component lack
data-testid attributes required for stable Playwright E2E test selectors. Add a
data-testid attribute to the form element with className "bruno-form" (for
example, something like "newAppForm" or "newApp-modal") and add a data-testid
attribute to the input element with id "appName" (for example, "appName-input"
or "newApp-nameInput") to provide explicit, stable selectors for E2E testing.
- Around line 47-49: Replace the useEffect hook containing setTimeout with a
native autoFocus attribute on the input element. Remove the entire useEffect
block that calls inputRef.current?.focus() with a 50ms delay, and instead add
the autoFocus attribute directly to the input element that inputRef was
referencing. This eliminates unnecessary useEffect usage per the coding
guidelines. Note that the same pattern appears in another location (lines 67-72)
and should be fixed there as well.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 1820-1823: The code at line 1820-1822 retrieves a parent item
using findItemInCollection based on itemUid, but does not validate that the
resolved parent is a folder-type item before accessing its items property. If
itemUid resolves to a non-folder item (like a file), then parent.items will be
undefined and parent.pathname will point to a file rather than a directory,
causing path construction failures at line 1838-1842 when creating child items.
After finding the parent item, validate that it is a folder type or directory;
if itemUid resolves to a non-folder item, traverse up to its containing folder
instead of using the non-folder item directly for computing parentPath and
siblings.

In `@packages/bruno-filestore/src/formats/bru/index.ts`:
- Around line 139-153: In the code block that handles standalone app items where
the type === 'app' check is made, the bruJson object is missing the settings
property which causes app settings to be lost during save/reload cycles. Add a
settings property to the bruJson object by extracting it from the incoming json
using _.get(json, 'settings') and include it alongside the meta and app
properties in the bruJson structure that gets passed to jsonToBruV2.

In `@packages/bruno-filestore/src/formats/yml/parseItem.ts`:
- Around line 91-93: Replace the unsafe `as any` type cast on ocItem in the
'app' case with a proper type annotation that matches the shape expected by
parseApp, similar to how other cases in the switch statement use specific type
casts like `as HttpRequest` or `as GraphQLRequest`. Either export an AppFile
type from parseApp.ts and import it here to use as the cast, or define a type
that represents the expected shape of the YML object for the app case and cast
ocItem to that type instead of using any.

In `@tests/apps/collection-apps.spec.ts`:
- Around line 15-26: The guestEval function currently selects the first webview
matching the data:text/html pattern globally, which causes non-determinism when
multiple preview webviews exist across different CollectionApp instances. Modify
the guestEval function to anchor the guest webview resolution to a specific
CollectionApp instance rather than searching globally. The webview selection
logic in the find method needs to be updated to filter based on the parent
window or app context so that it consistently targets the correct webview
belonging to the intended CollectionApp instance, making the test deterministic
and parallel-safe.

In `@tests/utils/page/actions.ts`:
- Around line 2046-2057: The folder branch in the conditional block uses global
name-only locators (locators.sidebar.folder(parent.folderName)) which can match
duplicate folder names across different collections, causing test flakiness in
parallel execution. Refactor the if block that handles parent.folderName to
scope the folder locators to the parent collection, similar to how the else
block scopes collection actions using parent.collectionName. This ensures the
folder hover and action click target the correct folder within the specified
collection context rather than relying on global folder name matching.

---

Nitpick comments:
In `@packages/bruno-filestore/src/formats/yml/items/parseApp.ts`:
- Around line 14-35: The parseApp function silently accepts a missing ocApp.info
field by defaulting to an empty object, which can hide malformed input. Add a
validation check at the beginning of the parseApp function to ensure that
ocApp.info is present and defined; if it's missing and required by the AppFile
contract, throw a descriptive error early before proceeding with the rest of the
function logic and object creation.
🪄 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: 51c5ddfc-10d8-4e7e-b660-6b4f27e16ef1

📥 Commits

Reviewing files that changed from the base of the PR and between 3f69977 and 1fc4bd5.

📒 Files selected for processing (25)
  • packages/bruno-app/src/components/AppView/EmptyAppState.js
  • packages/bruno-app/src/components/AppView/index.js
  • packages/bruno-app/src/components/AppView/webview-bridge.js
  • packages/bruno-app/src/components/CollectionApp/StyledWrapper.js
  • packages/bruno-app/src/components/CollectionApp/index.js
  • packages/bruno-app/src/components/RequestTabPanel/index.js
  • packages/bruno-app/src/components/RequestTabs/RequestTab/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/CollectionItemIcon/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
  • packages/bruno-app/src/components/Sidebar/NewApp/index.js
  • packages/bruno-app/src/providers/ReduxStore/middlewares/draft/middleware.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
  • packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
  • packages/bruno-app/src/utils/collections/index.js
  • packages/bruno-electron/src/ipc/collection.js
  • packages/bruno-filestore/src/formats/bru/index.ts
  • packages/bruno-filestore/src/formats/yml/items/parseApp.ts
  • packages/bruno-filestore/src/formats/yml/items/stringifyApp.ts
  • packages/bruno-filestore/src/formats/yml/parseItem.ts
  • packages/bruno-filestore/src/formats/yml/stringifyItem.ts
  • packages/bruno-schema-types/src/collection/item.ts
  • packages/bruno-schema/src/collections/index.js
  • tests/apps/collection-apps.spec.ts
  • tests/utils/page/actions.ts

Comment thread packages/bruno-app/src/components/AppView/EmptyAppState.js
Comment thread packages/bruno-app/src/components/Sidebar/NewApp/index.js Outdated
Comment thread packages/bruno-app/src/components/Sidebar/NewApp/index.js Outdated
Comment thread packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js Outdated
Comment thread packages/bruno-filestore/src/formats/bru/index.ts
Comment thread packages/bruno-filestore/src/formats/yml/parseItem.ts
Comment thread tests/apps/collection-apps.spec.ts Outdated
Comment thread tests/utils/page/actions.ts
utkarsh-bruno
utkarsh-bruno previously approved these changes Jun 23, 2026

@utkarsh-bruno utkarsh-bruno left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Since the app at collection level gets listed under collection, should the search bar on the side also return the apps ?
If yes, then doesCollectionHaveItemsMatchingSearchText() method might need some change.
Rest looks fine to me.

@naman-bruno naman-bruno merged commit 84c53b6 into usebruno:main Jun 24, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants