Skip to content

feat: add Monaco Editor as beta alternative for script editors#7793

Open
sanish-bruno wants to merge 6 commits intousebruno:mainfrom
sanish-bruno:feat/monaco-editor
Open

feat: add Monaco Editor as beta alternative for script editors#7793
sanish-bruno wants to merge 6 commits intousebruno:mainfrom
sanish-bruno:feat/monaco-editor

Conversation

@sanish-bruno
Copy link
Copy Markdown
Collaborator

@sanish-bruno sanish-bruno commented Apr 17, 2026

Jira

Summary

  • Adds Monaco Editor (VS Code's editor) as a beta feature for script editing and request body editing
  • Users can toggle between CodeMirror and Monaco via Preferences > Beta > Monaco Editor
  • Both editors remain available — CodeMirror continues to be the default

What's included

  • MonacoEditor component with full prop compatibility with the existing CodeEditor
  • ScriptEditor switcher that renders Monaco or CodeMirror based on the beta feature flag
  • Request body editor also switches to Monaco when the beta flag is enabled
  • Custom Bruno themes — maps all CodeMirror token colors (syntax highlighting) to Monaco for both light and dark themes
  • Bruno API intellisense — full TypeScript declarations for bru, req, res objects with JSDoc, overloads, and @example tags
  • Variable highlighting{{var}} patterns highlighted green (valid), red (invalid), blue (prompt) with hover tooltips showing variable value, scope, and inline editing (gated by enableVariableHighlighting prop — enabled for body editors, disabled for scripts)
  • Variable autocomplete{{ triggers variable suggestions across all editor languages (JSON, XML, plaintext, JavaScript, etc.), including environment/collection/runtime variables, $mockFunction names, and process.env.* keys
  • Dynamic variable tooltips{{$randomInt}} etc. show "Generates random value on each request" (or "Generates current timestamp on each request" for time-based vars) matching CodeMirror behavior — no value input or copy button
  • Scope-aware variable creation — new variables default to request/folder/collection scope based on editor context, matching CodeMirror's brunoVarInfo behavior
  • Widget theming — hover tooltips, suggest widget, and parameter hints styled to match Bruno's theme
  • Keybindings — Cmd/Ctrl+Enter to run, Cmd/Ctrl+S to save, built-in Cmd+F search
  • Web worker configuration — Rsbuild configured for Monaco's language service workers
  • Unit tests — 20 tests covering MonacoEditor, ScriptEditor switcher, and language mapping

Architecture

CodeEditor (unchanged, CodeMirror — fallback when beta is off)
MonacoEditor (new — used when beta is on)

ScriptEditor (switcher — used by 6 script editor locations)
  ├── CodeEditor (CodeMirror) — when beta is off (default)
  └── MonacoEditor — when beta is on

RequestBody (also switches based on beta flag)
  ├── CodeEditor (CodeMirror) — when beta is off (default)
  └── MonacoEditor — when beta is on

Feature parity with CodeMirror

Feature CodeMirror Monaco
Variable text coloring ({{var}}) Via overlay mode (enableVariableHighlighting) Via decorations (enableVariableHighlighting)
Variable hover tooltip brunoVarInfo option setupVariableTooltip
Variable autocomplete inside {{}} setupAutoComplete (keyup-based) registerCompletionItemProvider + content change trigger
API intellisense (bru., req., res.) Static hint lists TypeScript type definitions (brunoApiTypes.js)
Inline variable editing Click-to-edit in tooltip Click-to-edit textarea in tooltip
Dynamic variable display Note only, no value input Note only, no value input
Scope fallback for new variables request/folder/collection based on context request/folder/collection based on context

Files changed

  • 6 script consumers pass item/folder for correct scope detection: RequestPane/Script, RequestPane/Tests, FolderSettings/Script, FolderSettings/Tests, CollectionSettings/Script, CollectionSettings/Tests
  • RequestPane/RequestBody switches between CodeEditor and MonacoEditor based on beta flag

Deferred (not in this PR)

  • Monaco for other editors (response viewer, documentation, GraphQL)
  • Custom Monarch tokenizer for full {{var}} syntax integration
  • GraphQL language support

Before (CodeMirror)

Screen.Recording.2026-04-17.at.5.44.46.PM.mov

After (Monaco)

Screen.Recording.2026-04-17.at.5.45.15.PM.mov

Variable highlighting

Screen.Recording.2026-04-18.at.1.07.23.AM.mov

Test plan

  • Open Preferences > Beta > enable "Monaco Editor (Beta)"
  • Open a request's Script tab — Monaco renders with JS syntax highlighting
  • Type bru. — autocomplete shows all Bruno API methods with JSDoc
  • Type {{varName}} in script — no text coloring, but autocomplete suggests variables
  • Switch to Body tab (JSON) — Monaco renders with variable highlighting (green/red)
  • Type {{ in body — variable autocomplete suggests env/collection/runtime variables
  • Type {{ho — suggestions filter to matching variables (e.g., host)
  • Hover over a {{var}} in body — tooltip shows variable value, scope, and allows editing
  • Hover over {{$randomInt}} — tooltip shows "Generates random value on each request" with no value input
  • Hover over undefined {{newVar}} — tooltip shows "Request" scope, allows adding a value
  • Cmd/Ctrl+Enter runs the request, Cmd/Ctrl+S saves
  • Switch theme (light/dark) — Monaco theme updates
  • Disable the beta feature — CodeMirror restored everywhere

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.

Summary by CodeRabbit

  • New Features

    • Monaco Editor available as a beta toggle — optional replacement editor across scripts, tests, request bodies and folders.
    • Monaco adds a theme, variable highlighting, rich variable tooltips (inspect/edit/mask/copy), autocomplete for {{...}} variables, and keyboard shortcuts for Run (Ctrl/Cmd+Enter) and Save (Ctrl/Cmd+S).
  • Chores

    • Added the Monaco dependency and updated build/runtime wiring.
  • Tests

    • Added unit tests for editor selection, Monaco integration, and language mapping.

- Added Monaco Editor as a beta feature for scripting in pre-request, post-response, and test scripts.
- Updated ScriptEditor component to conditionally render MonacoEditor or CodeEditor based on feature flag.
- Implemented variable highlighting and custom theme registration for Monaco.
- Updated package-lock.json to include new dependencies for Monaco Editor.
- Enhanced user experience with improved autocomplete and multi-cursor editing capabilities.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds opt-in Monaco Editor: dependency and bundler rule, a new MonacoEditor component with theme/types/autocomplete/variable tooltip/workers, a ScriptEditor wrapper toggled by a beta flag, replaces CodeEditor usages with ScriptEditor across several UIs, updates preferences/schema, and adds tests.

Changes

Cohort / File(s) Summary
Dependency & Bundler
packages/bruno-app/package.json, packages/bruno-app/rsbuild.config.mjs
Adds monaco-editor dependency and an rspack rule to load Monaco worker modules with dynamicImportMode: 'lazy'.
Monaco Editor UI
packages/bruno-app/src/components/MonacoEditor/index.js, packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js, packages/bruno-app/src/components/MonacoEditor/index.spec.js
New MonacoEditor component (init, lifecycle, keybindings, theme/types registration, scroll persistence), styled wrapper, and unit tests.
ScriptEditor Wrapper
packages/bruno-app/src/components/ScriptEditor/index.js, packages/bruno-app/src/components/ScriptEditor/index.spec.js
New ScriptEditor that conditionally renders CodeEditor or lazy-loaded MonacoEditor based on MONACO_EDITOR beta flag; includes tests for selection and prop forwarding.
Editor Replacements
multiple editor usage files
packages/bruno-app/src/components/.../Script/index.js, .../Tests/index.js (CollectionSettings, FolderSettings, RequestPane, RequestBody, RequestPane/Tests, FolderSettings/Script, CollectionSettings/Script)
Replaces CodeEditor with ScriptEditor in many editor entry points and adds item prop where appropriate; preserves existing props and handlers.
Monaco Utilities
packages/bruno-app/src/utils/monaco/...
brunoApiTypes.js, brunoTheme.js, languageMapping.js, languageMapping.spec.js, variableHighlighting.js, autocomplete.js, workers.js
New utilities: Bruno API TypeScript declarations, Bruno-derived Monaco theme, CodeMirror→Monaco mode mapping (with tests), variable decorations and interactive tooltip, {{...}} autocomplete provider, and Monaco worker routing.
Global Styles
packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js, packages/bruno-app/src/globalStyles.js
Adds Monaco editor container styling and CSS for variable tooltip/editor textarea.
Beta Feature & Preferences
packages/bruno-app/src/utils/beta-features.js, packages/bruno-app/src/components/Preferences/Beta/index.js, packages/bruno-electron/src/store/preferences.js
Adds MONACO_EDITOR beta flag and preferences entry, updates UI list and preference schema/defaults.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as "App UI\n(ScriptEditor)"
  participant Script as "ScriptEditor"
  participant Editor as "MonacoEditor"
  participant Monaco as "monaco.editor"
  participant Worker as "Monaco Worker Loader"
  participant Store as "Redux Store"

  UI->>Script: render(props, ref)
  Script->>Editor: lazy-load (if beta enabled)
  Editor->>Monaco: register theme & types (brunoTheme, brunoApiTypes)
  Editor->>Worker: ensure getWorker routes (json/js/ts/worker)
  Editor->>Monaco: create(editorContainer, options)
  Monaco-->>Editor: editor instance
  Editor->>Editor: setupVariableHighlighting + setupAutoComplete
  UI->>Editor: user edits -> onEdit callback
  Editor->>Store: dispatch update/save/run actions
  UI->>Editor: unmount
  Editor->>Monaco: dispose editor, remove listeners, persist scroll
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

Monaco hums in themes of hue,
Variables wink and tooltips view,
Workers spin and suggestions chime,
Flip a beta, editors climb,
Code and comfort, one commit anew ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 primary change: introducing Monaco Editor as a beta alternative to CodeMirror for script editors.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 and usage tips.

@sanish-bruno sanish-bruno changed the title feat: integrate Monaco Editor for enhanced scripting experience feat: add Monaco Editor as beta alternative for script editors Apr 17, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/bruno-app/src/components/CollectionSettings/Script/index.js (1)

42-52: ⚠️ Potential issue | 🟠 Major

Downstream of the ScriptEditor ref issue — preRequestEditorRef.current / postResponseEditorRef.current will be null.

The useEffect on Lines 42-52 calls editor.refresh() on tab change to avoid CodeMirror layout glitches. After the swap to ScriptEditor (which doesn't forward refs), both refs stay null and the optional chaining silently no-ops. Fix lives in ScriptEditor/index.js (use forwardRef); once that lands, this file needs no further change.

Also applies to: 103-104, 118-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/CollectionSettings/Script/index.js` around
lines 42 - 52, useEffect in this file calls
preRequestEditorRef.current.editor.refresh() /
postResponseEditorRef.current.editor.refresh() but those refs are null because
the new ScriptEditor component doesn't forward refs; fix by updating
ScriptEditor (ScriptEditor/index.js) to use React.forwardRef and attach the
inner editor instance (or an object with an .editor property) to the forwarded
ref so preRequestEditorRef and postResponseEditorRef receive the expected shape
when ScriptEditor mounts.
packages/bruno-app/src/components/FolderSettings/Script/index.js (1)

105-132: ⚠️ Potential issue | 🟡 Minor

MonacoEditor doesn't forward refs, so preRequestEditorRef and postResponseEditorRef will be null when the MONACO_EDITOR beta flag is enabled.

ScriptEditor conditionally renders either CodeEditor (class component) or MonacoEditor (function component) and spreads all props through. MonacoEditor has no forwardRef wrapper, so refs won't populate. The useEffect at line 43 calls .editor.refresh() (CodeMirror-only API), which silently no-ops for Monaco due to optional chaining, and React warns about unforwarded refs on function components (React 18+).

Either wrap MonacoEditor with forwardRef and expose { editor: { refresh() } } (noop is fine—Monaco uses automaticLayout: true), or conditionally omit refs when rendering Monaco.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/FolderSettings/Script/index.js` around
lines 105 - 132, ScriptEditor currently forwards refs to both CodeEditor and
MonacoEditor but MonacoEditor is a function component without forwardRef so
preRequestEditorRef/postResponseEditorRef end up null and the useEffect that
calls .editor.refresh() (line ~43) triggers React warnings; fix by either
wrapping MonacoEditor with React.forwardRef and exposing an object with
editor.refresh() (noop is acceptable) so the ref shape matches CodeEditor, or
detect the MONACO_EDITOR path in ScriptEditor and do not pass ref props to
MonacoEditor; update MonacoEditor (or ScriptEditor) so refs are consistent with
the expected { editor: { refresh() } } shape referenced by the useEffect.
♻️ Duplicate comments (1)
packages/bruno-app/src/components/RequestPane/Script/index.js (1)

37-48: ⚠️ Potential issue | 🟠 Major

Same ref regression as CollectionSettings/Script — refresh-on-tab-switch becomes a no-op.

preRequestEditorRef/postResponseEditorRef won't be populated until ScriptEditor forwards its ref. Root-cause fix is in ScriptEditor/index.js; no change required here afterwards.

Also applies to: 99-100, 115-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines
37 - 48, The refresh-on-tab-switch is a no-op because ScriptEditor does not
forward its ref, so preRequestEditorRef and postResponseEditorRef never get
populated; fix by updating ScriptEditor (the component in ScriptEditor/index.js)
to use React.forwardRef and pass the received ref through to the inner
ScriptEditor instance or the underlying editor component (ensuring the forwarded
ref points to an object with .editor), i.e., accept (props, ref) and
attach/ref={ref} (or assign ref.current = innerRef) so that
preRequestEditorRef.current.editor and postResponseEditorRef.current.editor are
defined and the useEffect refresh logic in RequestPane/Script works as intended.
🧹 Nitpick comments (8)
packages/bruno-app/src/utils/monaco/brunoApiTypes.js (2)

49-66: Optional: give expect/test a minimal chai-like shape instead of any.

Typing expect as any silences diagnostics but also kills the main value prop here — autocomplete for .to.equal(...), .to.be.a(...), etc. A lightweight structural type (even a loose ChaiExpect-like interface covering the common chainables you actually want to surface) would make the beta feel materially better. Not blocking.

🧠 Sketch
-declare const expect: any;
+interface BrunoAssertion {
+  to: BrunoAssertion;
+  be: BrunoAssertion;
+  have: BrunoAssertion;
+  a(type: string): BrunoAssertion;
+  equal(value: any): BrunoAssertion;
+  eql(value: any): BrunoAssertion;
+  include(value: any): BrunoAssertion;
+  property(name: string, value?: any): BrunoAssertion;
+  // ...extend as needed
+}
+declare const expect: (value: any) => BrunoAssertion;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/brunoApiTypes.js` around lines 49 - 66,
The current declarations declare expect as any which disables IDE autocomplete;
replace it with a minimal structural type (e.g., declare an interface like
ChaiExpect/ChaiAssertion exposing the common chain properties and methods you
want: .to and .be as nested objects that return an assertion type, and assertion
methods like equal(expected: any): void, a(type: string): void, eql(expected:
any): void, etc.) and then change "declare const expect: any;" to "declare const
expect: ChaiExpect;" (or the chosen name); keep the test declaration as-is but
ensure any async return types remain supported (declare function test(name:
string, fn: () => void | Promise<void>): void;). Use the existing symbols expect
and test to locate where to replace the any with the new minimal interface so
editors can surface chained completions without full Chai typing.

19-642: Move the .d.ts payload out of the JS string literal.

Keeping ~600 lines of TypeScript declarations inside a backtick template works, but it nukes editor tooling on the source-of-truth itself (no TS diagnostics/formatting, escaped backticks in JSDoc examples are easy to miss). Consider extracting into bruno-api.d.ts and importing with a raw/text loader (rsbuild supports this via output.dataUriLimit / source.assetsInclude or a ?raw import). Improves maintainability as the API surface grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/brunoApiTypes.js` around lines 19 - 642,
The large TypeScript declaration string assigned to the typeDefs constant in
brunoApiTypes.js should be moved into a separate .d.ts file (e.g.,
bruno-api.d.ts) and loaded as raw text at build time; create the .d.ts
containing the declarations, update the build config or enable raw imports
(esbuild source.assetsInclude or ?raw import) and replace the template literal
in the typeDefs declaration with an import of the raw file (assign the imported
string to typeDefs), and remove the in-file backtick block so the editor gets
real TS diagnostics and formatting.
packages/bruno-app/src/utils/monaco/variableHighlighting.js (1)

29-29: Nit: maskedEnvVariables is destructured but never used.

Pull it out of the destructure (or start using it to mask hover values — secrets in hover tooltips would be an actual problem). Same pattern on line 133.

Also applies to: 133-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` at line 29, The
destructured variable maskedEnvVariables in variableHighlighting.js (from the
line that declares const { pathParams = {}, maskedEnvVariables = [],
...varLookup } = variables) is unused; either remove maskedEnvVariables from the
destructure or implement masking logic so hover/tooltips do not reveal secrets
(also fix the identical unused destructure at the later occurrence around line
133). Update the destructure to drop maskedEnvVariables if you won't use it, or
use maskedEnvVariables in the hover/tooltip generation functions (search for
varLookup usage and the hover rendering code in this module) to replace/mask any
environment variable values before they are displayed.
packages/bruno-app/rsbuild.config.mjs (1)

35-44: Nit: trailing comma in rules array.

Per the repo style ("No trailing commas"), drop the comma after the closing } on Line 43.

✂️ Proposed fix
         rules: [
           {
             test: /monaco-editor\/esm\/vs\/.*\.worker/,
             parser: {
               javascript: {
                 dynamicImportMode: 'lazy'
               }
             }
-          },
+          }
         ],

As per coding guidelines: "No trailing commas."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/rsbuild.config.mjs` around lines 35 - 44, Remove the
trailing comma after the rules array entry: locate the rules array object whose
test is /monaco-editor\/esm\/vs\/.*\.worker/ (the object containing
parser.javascript.dynamicImportMode = 'lazy') and delete the comma following its
closing brace so the array ends without a trailing comma to comply with the "No
trailing commas" style rule.
packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js (1)

16-20: Overriding Monaco internal selectors with !important is fragile.

Targeting .monaco-editor .margin-view-overlays .line-numbers bypasses Monaco's theming API. If Monaco changes its DOM structure/class names in a minor version bump, this silently stops working. Prefer lineNumbersMinChars / lineDecorationsWidth options on the editor (already set in MonacoEditor/index.js) and drop this override if possible, or at least add a comment pinning the Monaco version this was validated against.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js` around lines
16 - 20, Remove the fragile CSS override in StyledWrapper.js that targets
".monaco-editor .margin-view-overlays .line-numbers" and instead rely on the
editor options already configured in MonacoEditor/index.js (lineNumbersMinChars
and lineDecorationsWidth); if you cannot remove the override for layout reasons,
replace it with a clear comment in StyledWrapper.js noting the exact Monaco
version this override was validated against and why it’s needed, and add a TODO
to remove it when upgrading Monaco—reference the ".monaco-editor
.margin-view-overlays .line-numbers" selector and the MonacoEditor/index.js
settings in your change.
packages/bruno-app/src/components/MonacoEditor/index.js (1)

145-157: Variable-highlighting effect clobbers the mount-time setup on first render.

The mount effect at Line 117–121 already wires up setupVariableHighlighting / registerVariableHoverProvider. This effect then fires immediately after mount with the same collection/item, tears the pair down, and re-creates them. Harmless but duplicate work on every editor mount.

Consider moving the setup solely into this effect (which also handles updates), or guard it with an initialized ref so it skips the first run.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MonacoEditor/index.js` around lines 145 -
157, The variable-highlighting useEffect that depends on collection and item
immediately runs after the mount effect and unnecessarily tears down and
re-creates highlighting; modify this by consolidating mount/setup into this
single useEffect (the one using editorRef, variableCleanupRef, hoverProviderRef)
so it handles both initial mount and subsequent updates, or add an initialized
ref (e.g., initializedRef) checked at the top of this effect to skip the first
run so you don't call variableCleanupRef.current?.() and
hoverProviderRef.current?.dispose() on mount; ensure you still call
setupVariableHighlighting(editor, collection, item) and
registerVariableHoverProvider(collection, item) and store their
cleanup/disposable in variableCleanupRef.current and hoverProviderRef.current
respectively so updates continue to work.
packages/bruno-app/src/components/ScriptEditor/index.spec.js (1)

26-38: Mock store is missing slices ScriptEditor likely reads.

If ScriptEditor (or its children once not mocked) ever selects from state.tabs or other slices, these tests will crash. Consider adding minimal tabs / collections reducers to the mock store now to avoid future flakiness, or drop the redux Provider entirely and mock useSelector directly to return just the beta flag — leaner and more focused on the behaviour under test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/ScriptEditor/index.spec.js` around lines 26
- 38, The test's createMockStore currently only provides app.preferences.beta
('monaco-editor') but ScriptEditor (or its children) may read other slices like
state.tabs or state.collections causing brittle failures; either extend
createMockStore to include minimal stub reducers for tabs and collections (e.g.,
return sensible default shapes used by ScriptEditor) or remove the redux
Provider and mock useSelector directly to return only the beta flag — update
tests to reference createMockStore and ScriptEditor (or replace Provider usage)
so the component sees the expected slices during rendering.
packages/bruno-app/src/components/MonacoEditor/index.spec.js (1)

188-191: Consider asserting which keybindings are registered.

toHaveBeenCalledTimes(2) will silently pass if someone later swaps Ctrl+Enter for some other chord. Inspect the first argument to verify the CtrlCmd | Enter and CtrlCmd | KeyS chord values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MonacoEditor/index.spec.js` around lines
188 - 191, The test currently only checks mockAddCommand was called twice;
instead assert the specific key chords were registered: after calling
renderEditor({ onRun, onSave }) inspect mockAddCommand calls
(mockAddCommand.mock.calls or Jest's toHaveBeenNthCalledWith) and verify the
first argument for the onRun registration equals the CtrlCmd|Enter chord and the
first argument for the onSave registration equals the CtrlCmd|KeyS chord (use
your monaco enums/bitmasks used elsewhere), while still allowing the handler
callback to be any function; reference the test name 'registers keybindings for
onRun and onSave', the renderEditor helper, and mockAddCommand to locate where
to add these assertions.
🤖 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/package.json`:
- Line 63: MonacoEditor is currently statically imported causing the ~2MB bundle
to load for all users; change the import in ScriptEditor (where useMonaco/beta
flag is checked) to lazy-load the component instead of importing it from
MonacoEditor/index.js at module top-level: replace the static import with
React.lazy(() => import('components/MonacoEditor')) and render it only when the
beta flag/useMonaco is true inside a Suspense fallback, otherwise render
CodeEditor; ensure no other files keep a top-level import of MonacoEditor so the
bundle is deferred until the component is actually needed.

In `@packages/bruno-app/src/components/MonacoEditor/index.js`:
- Around line 160-171: The external `value` sync currently calls editor.setValue
which resets selection and scroll; update the effect in useEffect to preserve
and restore editor state or apply edits instead: get the model via
editor.getModel(), capture current selections via editor.getSelections() and
scrollTop via editor.getScrollTop(), then either replace text using
model.pushEditOperations (or model.applyEdits) to update content without
resetting caret/viewport, or if setValue must be used, call editor.setValue(...)
and immediately restore selections with editor.setSelections(...) and scroll
with editor.setScrollTop(...); reference editorRef.current,
cachedValueRef.current, editor.getPosition / setValue, and
model.pushEditOperations when making the change.
- Around line 54-143: Remove the redundant registerBrunoTheme call from the
mount useEffect (the one that runs editor creation) and let the existing
theme-sync effect be the single source of truth; during editor creation
(monaco.editor.create) do not call registerBrunoTheme — instead either set a
stable initial theme name or omit the theme option and after create call
monaco.editor.setTheme(...) (or editor.updateOptions({ theme: stableName }))
from the theme-sync effect where registerBrunoTheme and setTheme already run so
Monaco only gets a single, stable theme registration; update references to
registerBrunoTheme, monaco.editor.create, monaco.editor.setTheme, and
editor.updateOptions accordingly.
- Around line 216-228: The returned JSX is missing a data-testid on the editor
container (blocking Playwright) and is passing dead props (font, fontSize) into
StyledWrapper which aren't consumed; update the component (MonacoEditor) so the
inner div with ref={containerRef} gets a data-testid (e.g.,
"monaco-editor-container") for tests, and either remove the unused font and
fontSize props from the StyledWrapper invocation or wire them into
StyledWrapper.js (e.g., read props.font/props.fontSize in StyledWrapper) so they
are actually applied—adjust whichever approach matches project conventions and
update any related prop typings.

In `@packages/bruno-app/src/components/MonacoEditor/index.spec.js`:
- Around line 214-217: The test currently only asserts mockGetDomNode was
called; update the spec to spy on the returned element's classList.add and
assert it was called with 'mousetrap' by making mockGetDomNode return an object
whose querySelector returns { classList: { add: jest.fn() } }, call
renderEditor(), then assert that the captured classList.add spy was invoked with
'mousetrap' (reference mocks: mockGetDomNode, renderEditor, and the element's
classList.add).

In `@packages/bruno-app/src/components/ScriptEditor/index.js`:
- Around line 6-10: The current ScriptEditor chooses Editor = useMonaco ?
MonacoEditor : CodeEditor which re-mounts the editor when the
BETA_FEATURES.MONACO_EDITOR flag toggles and loses in-memory state; change
ScriptEditor to render both MonacoEditor and CodeEditor simultaneously (both
mounted) and switch visibility via CSS/props based on useMonaco so the active
editor is shown but the inactive one remains mounted and preserves state;
reference ScriptEditor, useMonaco (BETA_FEATURES.MONACO_EDITOR), MonacoEditor
and CodeEditor and ensure both receive the same props so their state is retained
across toggles.
- Around line 6-10: ScriptEditor currently drops any ref because it's a plain
function component; forward the ref through ScriptEditor to the chosen Editor
(use React.forwardRef and accept (props, ref)), so callers using
preRequestEditorRef / postResponseEditorRef receive the inner instance;
additionally update MonacoEditor to implement the same imperative API as
CodeEditor (expose an .editor property and a refresh() method) using
useImperativeHandle so the forwarded ref for the Monaco path supports
editor.refresh(), keeping the CodeEditor class ref behavior intact.

In `@packages/bruno-app/src/components/ScriptEditor/index.spec.js`:
- Around line 71-92: The test currently never verifies props because the mock is
registered after the ScriptEditor import; change the mock setup to run before
the component import and capture props with a jest.fn. Replace the
jest.isolateModules/doMock pattern with a top-level mock for
'components/CodeEditor' that delegates to a jest.fn (e.g., codeEditorMock) so
ScriptEditor uses it when rendered, then assert codeEditorMock was called with
an objectContaining({ mode: 'javascript', onEdit: mockOnEdit }) instead of only
checking getByTestId; ensure you reference ScriptEditor and the mocked
CodeEditor (codeEditorMock) in the assertions.

In `@packages/bruno-app/src/utils/monaco/brunoTheme.js`:
- Line 26: The themeName currently uses Date.now() which creates a new unique
name on every registerBrunoTheme call (const themeName =
`bruno-${displayedTheme}-${Date.now()}`), leaking Monaco theme registrations;
change registerBrunoTheme to use a deterministic name keyed by displayedTheme
(e.g., `bruno-${displayedTheme}`) and, if you need cache-busting when the
palette contents change, append a short content hash of the palette instead of
Date.now(); ensure the same name is reused so monaco.editor.defineTheme is
idempotent and themes are not continuously accumulated.
- Around line 22-24: normalizeColor can return undefined for hsl(...) inputs so
editorBg, editorFg, and gutterBgNorm may be undefined when assigned and then
passed into Monaco (e.g., colors['editor.background']), causing Monaco to ignore
them; fix by making normalizeColor return a concrete theme-aware hex fallback
(use the theme param to choose a light/dark default) or, alternatively, ensure
each assignment uses a sensible fallback (e.g., const editorBg =
normalizeColor(bg) || '#ffffff' for light theme / '#0b0b0b' for dark) where
editorBg, editorFg, and gutterBgNorm are computed (also apply same pattern at
the other occurrences around lines 58-60); reference normalizeColor, editorBg,
editorFg, gutterBgNorm, and the colors['editor.background'] assignments when
applying the change.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Line 47: The expression using
mockDataFunctions.hasOwnProperty(varName.substring(1)) is unsafe and triggers
no-prototype-builtins; update the checks that reference mockDataFunctions (e.g.,
the isMockVariable assignment and the other occurrence noted) to use a safe
own-property test such as Object.hasOwn(mockDataFunctions, varName.substring(1))
(or Object.prototype.hasOwnProperty.call(mockDataFunctions,
varName.substring(1)) if you need older runtimes), keeping varName and
mockDataFunctions as the referenced symbols.
- Around line 24-70: The editor decoration update is triggered on every
keystroke by editor.onDidChangeModelContent causing expensive work in
updateDecorations (getAllVariables, model.getValue, VARIABLE_REGEX.exec,
model.getPositionAt, editor.deltaDecorations) — wrap calls to updateDecorations
in a short debounce (≈100–150ms) so rapid keystrokes batch into one run;
implement by keeping a timeout id scoped next to updateDecorations, call
clearTimeout and setTimeout in the onDidChangeModelContent handler (or use an
existing debounce util) and ensure the initial call still runs immediately to
populate decorations.

---

Outside diff comments:
In `@packages/bruno-app/src/components/CollectionSettings/Script/index.js`:
- Around line 42-52: useEffect in this file calls
preRequestEditorRef.current.editor.refresh() /
postResponseEditorRef.current.editor.refresh() but those refs are null because
the new ScriptEditor component doesn't forward refs; fix by updating
ScriptEditor (ScriptEditor/index.js) to use React.forwardRef and attach the
inner editor instance (or an object with an .editor property) to the forwarded
ref so preRequestEditorRef and postResponseEditorRef receive the expected shape
when ScriptEditor mounts.

In `@packages/bruno-app/src/components/FolderSettings/Script/index.js`:
- Around line 105-132: ScriptEditor currently forwards refs to both CodeEditor
and MonacoEditor but MonacoEditor is a function component without forwardRef so
preRequestEditorRef/postResponseEditorRef end up null and the useEffect that
calls .editor.refresh() (line ~43) triggers React warnings; fix by either
wrapping MonacoEditor with React.forwardRef and exposing an object with
editor.refresh() (noop is acceptable) so the ref shape matches CodeEditor, or
detect the MONACO_EDITOR path in ScriptEditor and do not pass ref props to
MonacoEditor; update MonacoEditor (or ScriptEditor) so refs are consistent with
the expected { editor: { refresh() } } shape referenced by the useEffect.

---

Duplicate comments:
In `@packages/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 37-48: The refresh-on-tab-switch is a no-op because ScriptEditor
does not forward its ref, so preRequestEditorRef and postResponseEditorRef never
get populated; fix by updating ScriptEditor (the component in
ScriptEditor/index.js) to use React.forwardRef and pass the received ref through
to the inner ScriptEditor instance or the underlying editor component (ensuring
the forwarded ref points to an object with .editor), i.e., accept (props, ref)
and attach/ref={ref} (or assign ref.current = innerRef) so that
preRequestEditorRef.current.editor and postResponseEditorRef.current.editor are
defined and the useEffect refresh logic in RequestPane/Script works as intended.

---

Nitpick comments:
In `@packages/bruno-app/rsbuild.config.mjs`:
- Around line 35-44: Remove the trailing comma after the rules array entry:
locate the rules array object whose test is /monaco-editor\/esm\/vs\/.*\.worker/
(the object containing parser.javascript.dynamicImportMode = 'lazy') and delete
the comma following its closing brace so the array ends without a trailing comma
to comply with the "No trailing commas" style rule.

In `@packages/bruno-app/src/components/MonacoEditor/index.js`:
- Around line 145-157: The variable-highlighting useEffect that depends on
collection and item immediately runs after the mount effect and unnecessarily
tears down and re-creates highlighting; modify this by consolidating mount/setup
into this single useEffect (the one using editorRef, variableCleanupRef,
hoverProviderRef) so it handles both initial mount and subsequent updates, or
add an initialized ref (e.g., initializedRef) checked at the top of this effect
to skip the first run so you don't call variableCleanupRef.current?.() and
hoverProviderRef.current?.dispose() on mount; ensure you still call
setupVariableHighlighting(editor, collection, item) and
registerVariableHoverProvider(collection, item) and store their
cleanup/disposable in variableCleanupRef.current and hoverProviderRef.current
respectively so updates continue to work.

In `@packages/bruno-app/src/components/MonacoEditor/index.spec.js`:
- Around line 188-191: The test currently only checks mockAddCommand was called
twice; instead assert the specific key chords were registered: after calling
renderEditor({ onRun, onSave }) inspect mockAddCommand calls
(mockAddCommand.mock.calls or Jest's toHaveBeenNthCalledWith) and verify the
first argument for the onRun registration equals the CtrlCmd|Enter chord and the
first argument for the onSave registration equals the CtrlCmd|KeyS chord (use
your monaco enums/bitmasks used elsewhere), while still allowing the handler
callback to be any function; reference the test name 'registers keybindings for
onRun and onSave', the renderEditor helper, and mockAddCommand to locate where
to add these assertions.

In `@packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js`:
- Around line 16-20: Remove the fragile CSS override in StyledWrapper.js that
targets ".monaco-editor .margin-view-overlays .line-numbers" and instead rely on
the editor options already configured in MonacoEditor/index.js
(lineNumbersMinChars and lineDecorationsWidth); if you cannot remove the
override for layout reasons, replace it with a clear comment in StyledWrapper.js
noting the exact Monaco version this override was validated against and why it’s
needed, and add a TODO to remove it when upgrading Monaco—reference the
".monaco-editor .margin-view-overlays .line-numbers" selector and the
MonacoEditor/index.js settings in your change.

In `@packages/bruno-app/src/components/ScriptEditor/index.spec.js`:
- Around line 26-38: The test's createMockStore currently only provides
app.preferences.beta ('monaco-editor') but ScriptEditor (or its children) may
read other slices like state.tabs or state.collections causing brittle failures;
either extend createMockStore to include minimal stub reducers for tabs and
collections (e.g., return sensible default shapes used by ScriptEditor) or
remove the redux Provider and mock useSelector directly to return only the beta
flag — update tests to reference createMockStore and ScriptEditor (or replace
Provider usage) so the component sees the expected slices during rendering.

In `@packages/bruno-app/src/utils/monaco/brunoApiTypes.js`:
- Around line 49-66: The current declarations declare expect as any which
disables IDE autocomplete; replace it with a minimal structural type (e.g.,
declare an interface like ChaiExpect/ChaiAssertion exposing the common chain
properties and methods you want: .to and .be as nested objects that return an
assertion type, and assertion methods like equal(expected: any): void, a(type:
string): void, eql(expected: any): void, etc.) and then change "declare const
expect: any;" to "declare const expect: ChaiExpect;" (or the chosen name); keep
the test declaration as-is but ensure any async return types remain supported
(declare function test(name: string, fn: () => void | Promise<void>): void;).
Use the existing symbols expect and test to locate where to replace the any with
the new minimal interface so editors can surface chained completions without
full Chai typing.
- Around line 19-642: The large TypeScript declaration string assigned to the
typeDefs constant in brunoApiTypes.js should be moved into a separate .d.ts file
(e.g., bruno-api.d.ts) and loaded as raw text at build time; create the .d.ts
containing the declarations, update the build config or enable raw imports
(esbuild source.assetsInclude or ?raw import) and replace the template literal
in the typeDefs declaration with an import of the raw file (assign the imported
string to typeDefs), and remove the in-file backtick block so the editor gets
real TS diagnostics and formatting.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Line 29: The destructured variable maskedEnvVariables in
variableHighlighting.js (from the line that declares const { pathParams = {},
maskedEnvVariables = [], ...varLookup } = variables) is unused; either remove
maskedEnvVariables from the destructure or implement masking logic so
hover/tooltips do not reveal secrets (also fix the identical unused destructure
at the later occurrence around line 133). Update the destructure to drop
maskedEnvVariables if you won't use it, or use maskedEnvVariables in the
hover/tooltip generation functions (search for varLookup usage and the hover
rendering code in this module) to replace/mask any environment variable values
before they are displayed.
🪄 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: 89fc457a-5dc3-401a-9883-266fb77b22b4

📥 Commits

Reviewing files that changed from the base of the PR and between c6281d3 and 9a100e7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • packages/bruno-app/package.json
  • packages/bruno-app/rsbuild.config.mjs
  • packages/bruno-app/src/components/CollectionSettings/Script/index.js
  • packages/bruno-app/src/components/CollectionSettings/Tests/index.js
  • packages/bruno-app/src/components/FolderSettings/Script/index.js
  • packages/bruno-app/src/components/FolderSettings/Tests/index.js
  • packages/bruno-app/src/components/MonacoEditor/StyledWrapper.js
  • packages/bruno-app/src/components/MonacoEditor/index.js
  • packages/bruno-app/src/components/MonacoEditor/index.spec.js
  • packages/bruno-app/src/components/Preferences/Beta/index.js
  • packages/bruno-app/src/components/RequestPane/Script/index.js
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/components/ScriptEditor/index.js
  • packages/bruno-app/src/components/ScriptEditor/index.spec.js
  • packages/bruno-app/src/utils/beta-features.js
  • packages/bruno-app/src/utils/monaco/brunoApiTypes.js
  • packages/bruno-app/src/utils/monaco/brunoTheme.js
  • packages/bruno-app/src/utils/monaco/languageMapping.js
  • packages/bruno-app/src/utils/monaco/languageMapping.spec.js
  • packages/bruno-app/src/utils/monaco/variableHighlighting.js
  • packages/bruno-app/src/utils/monaco/workers.js
  • packages/bruno-electron/src/store/preferences.js

"mime-types": "^3.0.2",
"moment": "^2.30.1",
"moment-timezone": "^0.5.47",
"monaco-editor": "^0.55.1",
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how ScriptEditor imports MonacoEditor — static vs dynamic.
fd -t f 'index.js' packages/bruno-app/src/components/ScriptEditor | xargs rg -n -C3 -e "MonacoEditor" -e "React\.lazy" -e "import\("

Repository: usebruno/bruno

Length of output: 469


🏁 Script executed:

fd -t f 'index.js' packages/bruno-app/src/components/MonacoEditor | head -5 | xargs cat -n

Repository: usebruno/bruno

Length of output: 8586


🏁 Script executed:

rg -n "React\.lazy|dynamic.*import|import.*monaco" packages/bruno-app/src/components/MonacoEditor --type js --type jsx

Repository: usebruno/bruno

Length of output: 85


Lazy-load Monaco behind the beta flag.

monaco-editor is statically imported in MonacoEditor/index.js (line 3), which means the entire ~2MB minified bundle loads for all users on app startup—even though the feature is opt-in beta only. Currently, ScriptEditor conditionally renders either MonacoEditor or CodeEditor, but this conditional happens after the static import has already pulled the bundle.

Move the MonacoEditor import in ScriptEditor/index.js to use React.lazy(), and defer loading until the beta flag is actually enabled:

const MonacoEditor = lazy(() => import('components/MonacoEditor'));
const Editor = useMonaco ? <Suspense fallback={...}><MonacoEditor {...props} /></Suspense> : <CodeEditor {...props} />;

This prevents non-beta users from incurring the bundle cost on initial load.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/package.json` at line 63, MonacoEditor is currently
statically imported causing the ~2MB bundle to load for all users; change the
import in ScriptEditor (where useMonaco/beta flag is checked) to lazy-load the
component instead of importing it from MonacoEditor/index.js at module
top-level: replace the static import with React.lazy(() =>
import('components/MonacoEditor')) and render it only when the beta
flag/useMonaco is true inside a Suspense fallback, otherwise render CodeEditor;
ensure no other files keep a top-level import of MonacoEditor so the bundle is
deferred until the component is actually needed.

Comment thread packages/bruno-app/src/components/MonacoEditor/index.js
Comment thread packages/bruno-app/src/components/MonacoEditor/index.js
Comment thread packages/bruno-app/src/components/MonacoEditor/index.js
Comment thread packages/bruno-app/src/components/MonacoEditor/index.spec.js
Comment thread packages/bruno-app/src/components/ScriptEditor/index.spec.js
Comment thread packages/bruno-app/src/utils/monaco/brunoTheme.js
Comment thread packages/bruno-app/src/utils/monaco/brunoTheme.js Outdated
Comment thread packages/bruno-app/src/utils/monaco/variableHighlighting.js
Comment thread packages/bruno-app/src/utils/monaco/variableHighlighting.js Outdated
…d tooltip support

- Added variable autocomplete functionality to the Monaco Editor, providing suggestions for {{variable}} patterns.
- Implemented a rich tooltip system for variable hover interactions, improving user experience.
- Updated ScriptEditor and RequestBody components to utilize the new Monaco Editor features.
- Refactored variable highlighting and tooltip logic for better performance and maintainability.
- Added tests to ensure proper functionality of the new autocomplete and tooltip features.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/bruno-app/src/utils/monaco/variableHighlighting.js (3)

493-528: Tooltip doesn't follow or dismiss on editor scroll.

Popup uses position: fixed with coordinates computed at show time. Scrolling the editor moves the {{variable}} but the popup stays pinned, drifting away from its target. Consider subscribing to editor.onDidScrollChange to either reposition or hide the active popup (alongside the existing onDidChangeModelContent hide).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 493
- 528, The tooltip is positioned once (showTooltip) and uses fixed coordinates
so it drifts when the editor is scrolled; subscribe to the
editor.onDidScrollChange event and in that handler either call the existing
showTooltip logic to recompute and reposition the active popup (using
currentVarName/varInfo) or call hidePopup/startHideTimer to dismiss it; attach
the listener when setting up (alongside
onMouseMove/onMouseLeave/onDidChangeModelContent) and ensure you clear the
scroll listener when disposing so hoverTimeout/hideTimeout and activePopup are
correctly handled.

538-539: Import statement at bottom of file.

import { updateVariableInScope } from 'providers/ReduxStore/slices/collections/actions' sits at Line 539, below the comment at 538. ES module imports are hoisted so this works, but placing it at the bottom (presumably to paper over a circular dep with actions.js) is easy to miss, hostile to bundler tree-shaking heuristics, and tripwire for lint rules like import/first. Hoist it to the top with the other imports, and if it genuinely introduces a cycle, refactor the action into a cycle-free module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 538
- 539, Move the import "updateVariableInScope" out of the bottom of
variableHighlighting.js and place it with the other top-level imports so all ES
module imports are hoisted consistently; specifically, relocate the line
importing updateVariableInScope from
'providers/ReduxStore/slices/collections/actions' up into the module's import
block (near other imports in variableHighlighting.js) and run the linter/build;
if doing so exposes a circular dependency involving actions.js, extract the
updateVariableInScope logic (or a small dispatch wrapper) into a new cycle-free
module that both variableHighlighting.js and collections/actions can import to
break the cycle.

6-6: Duplicate inline {{…}} regex.

Line 124 re-declares the same pattern as the module-level VARIABLE_REGEX (minus g behavior since it's inlined for matchAll). Reuse the constant (or a shared factory) to keep regex maintenance in one place.

Also applies to: 124-124

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` at line 6, The
inline duplication of the same {{…}} pattern should be removed: replace the
ad-hoc regex used where matchAll is called with the module-level VARIABLE_REGEX
(or create a RegExp from VARIABLE_REGEX.source with the proper flags for
matchAll) so the pattern is maintained in one place; update the code that
currently declares the inline regex (used with matchAll) to reuse VARIABLE_REGEX
(or new RegExp(VARIABLE_REGEX.source, 'g') if specific flags are needed) so
maintenance and behavior are consistent.
packages/bruno-app/src/utils/monaco/autocomplete.js (2)

177-194: Redundant suggest trigger may double-fire.

triggerCharacters: ['{', '.', '$'] already covers the typical entry points. The onDidChangeModelContent handler additionally fires editor.action.triggerSuggest on every keystroke when the pattern matches — useful when typing word chars inside {{…, but it will also re-trigger while the widget is already open on each keystroke. Monaco tolerates this, but you can gate it to only fire when the widget isn't already visible (e.g., via editor.getContribution('editor.contrib.suggestController')) to avoid churn.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/autocomplete.js` around lines 177 - 194,
The onDidChangeModelContent handler (contentChangeDisposable) unconditionally
calls editor.action.triggerSuggest when VARIABLE_PATTERN matches, which can
re-trigger suggestions on every keystroke; update the handler to first check the
suggest widget visibility via the suggest controller
(editor.getContribution('editor.contrib.suggestController')) and only call
editor.trigger('bruno-autocomplete','editor.action.triggerSuggest',{}) when the
controller indicates the suggest widget is not currently visible. Ensure you
reference VARIABLE_PATTERN, contentChangeDisposable,
editor.onDidChangeModelContent and the suggest controller call to gate the
trigger.

128-135: getAllVariables recomputed on every keystroke.

Combined with incomplete: true and the content-change re-trigger at Lines 177‑194, getAllVariables(collection, item) — which walks the collection tree, merges envs, masked vars, oauth, path params, etc. — runs per keystroke per language provider. On large collections this adds up. Consider caching the lookup per model version or behind a short memoization keyed on (collection.uid, item?.uid, activeEnvironmentUid).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/autocomplete.js` around lines 128 - 135,
getAllVariables(collection, item) is being recomputed on every keystroke; wrap
it in a short-lived memoization/cache so you avoid walking the entire collection
per keystroke. Implement a module-level Map cache keyed by
`${collection.uid}:${item?.uid || ''}:${activeEnvironmentUid}` (or include the
editor/model version if available) and return cached allVariables when the key
matches; on cache miss compute via getAllVariables and store the result. Also
ensure the cache is invalidated/updated when collection/item/environment/model
version changes (e.g., clear entry when collection.uid or activeEnvironmentUid
changes) so buildVariableHints and subsequent filters use the cached lookup
instead of recomputing each keystroke.
🤖 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/components/MonacoEditor/index.js`:
- Around line 127-136: The mount effect currently initializes variable
highlighting/tooltips only using the initial enableVariableHighlighting value
and the re-apply effect (which references variableCleanupRef, tooltipCleanupRef
and setupVariableHighlighting/setupVariableTooltip) skips when
enableVariableHighlighting is false and lacks enableVariableHighlighting in its
dependency list; update the re-apply effect to include
enableVariableHighlighting in its deps and on change handle first-time setup and
cleanup: when enableVariableHighlighting becomes true call
setupVariableHighlighting(editor, collectionRef.current, itemRef.current) and
setupVariableTooltip(editor, () => collectionRef.current, () => itemRef.current,
dispatch), storing cleanup into variableCleanupRef.current and
tooltipCleanupRef.current; when it becomes false run any stored cleanups and
clear the refs so toggling post-mount properly enables/disables highlighting and
tooltips.

In `@packages/bruno-app/src/utils/monaco/autocomplete.js`:
- Line 7: The regex stored in VARIABLE_PATTERN currently excludes the question
mark so prompt-style variables like "{{?name" won't trigger completions; update
VARIABLE_PATTERN to allow '?' inside the character class (e.g., include '?' or
escape the hyphen appropriately) so "{{?..." matches, and add a short JSDoc
above VARIABLE_PATTERN describing that it intentionally includes prompt-style
prefixes (or, if you decide to leave it excluding '?', document that choice) to
make the intent clear for future readers.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Around line 453-457: The tooltip positioning divides pixel coordinates by 16
to produce rem units, which breaks placement when the root font-size differs;
instead remove the /16 conversion and set popup.style.top and popup.style.left
using pixel values (e.g., `${top}px` and `${left}px`) where popup, top, and left
are computed (via getBoundingClientRect()/getScrolledVisiblePosition()) so the
tooltip aligns correctly in all zoom/font-size settings.
- Around line 59-65: The captured variable name from VARIABLE_REGEX is not
trimmed, so whitespace like in "{{ foo }}" yields " foo " and fails scope
lookup; update the handling in variableHighlighting.js to use a trimmed name for
resolution by changing the usage of match[1] to match[1].trim() (e.g., replace
const varName = match[1]; with a trimmed variant) before any scope/variable
lookup and validation, but keep startOffset/endOffset and
model.getPositionAt(...) logic unchanged so highlighting ranges remain correct;
apply the same trim fix to the other occurrence around lines 123-130 as well.

---

Nitpick comments:
In `@packages/bruno-app/src/utils/monaco/autocomplete.js`:
- Around line 177-194: The onDidChangeModelContent handler
(contentChangeDisposable) unconditionally calls editor.action.triggerSuggest
when VARIABLE_PATTERN matches, which can re-trigger suggestions on every
keystroke; update the handler to first check the suggest widget visibility via
the suggest controller
(editor.getContribution('editor.contrib.suggestController')) and only call
editor.trigger('bruno-autocomplete','editor.action.triggerSuggest',{}) when the
controller indicates the suggest widget is not currently visible. Ensure you
reference VARIABLE_PATTERN, contentChangeDisposable,
editor.onDidChangeModelContent and the suggest controller call to gate the
trigger.
- Around line 128-135: getAllVariables(collection, item) is being recomputed on
every keystroke; wrap it in a short-lived memoization/cache so you avoid walking
the entire collection per keystroke. Implement a module-level Map cache keyed by
`${collection.uid}:${item?.uid || ''}:${activeEnvironmentUid}` (or include the
editor/model version if available) and return cached allVariables when the key
matches; on cache miss compute via getAllVariables and store the result. Also
ensure the cache is invalidated/updated when collection/item/environment/model
version changes (e.g., clear entry when collection.uid or activeEnvironmentUid
changes) so buildVariableHints and subsequent filters use the cached lookup
instead of recomputing each keystroke.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Around line 493-528: The tooltip is positioned once (showTooltip) and uses
fixed coordinates so it drifts when the editor is scrolled; subscribe to the
editor.onDidScrollChange event and in that handler either call the existing
showTooltip logic to recompute and reposition the active popup (using
currentVarName/varInfo) or call hidePopup/startHideTimer to dismiss it; attach
the listener when setting up (alongside
onMouseMove/onMouseLeave/onDidChangeModelContent) and ensure you clear the
scroll listener when disposing so hoverTimeout/hideTimeout and activePopup are
correctly handled.
- Around line 538-539: Move the import "updateVariableInScope" out of the bottom
of variableHighlighting.js and place it with the other top-level imports so all
ES module imports are hoisted consistently; specifically, relocate the line
importing updateVariableInScope from
'providers/ReduxStore/slices/collections/actions' up into the module's import
block (near other imports in variableHighlighting.js) and run the linter/build;
if doing so exposes a circular dependency involving actions.js, extract the
updateVariableInScope logic (or a small dispatch wrapper) into a new cycle-free
module that both variableHighlighting.js and collections/actions can import to
break the cycle.
- Line 6: The inline duplication of the same {{…}} pattern should be removed:
replace the ad-hoc regex used where matchAll is called with the module-level
VARIABLE_REGEX (or create a RegExp from VARIABLE_REGEX.source with the proper
flags for matchAll) so the pattern is maintained in one place; update the code
that currently declares the inline regex (used with matchAll) to reuse
VARIABLE_REGEX (or new RegExp(VARIABLE_REGEX.source, 'g') if specific flags are
needed) so maintenance and behavior are consistent.
🪄 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: eca7ed6c-f76c-43d4-841c-c60ebcb15b8d

📥 Commits

Reviewing files that changed from the base of the PR and between 9a100e7 and 671cec3.

📒 Files selected for processing (9)
  • packages/bruno-app/src/components/FolderSettings/Script/index.js
  • packages/bruno-app/src/components/FolderSettings/Tests/index.js
  • packages/bruno-app/src/components/MonacoEditor/index.js
  • packages/bruno-app/src/components/MonacoEditor/index.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/RequestPane/Script/index.js
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/utils/monaco/autocomplete.js
  • packages/bruno-app/src/utils/monaco/variableHighlighting.js
✅ Files skipped from review due to trivial changes (2)
  • packages/bruno-app/src/components/FolderSettings/Tests/index.js
  • packages/bruno-app/src/components/MonacoEditor/index.spec.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/components/RequestPane/Tests/index.js
  • packages/bruno-app/src/components/RequestPane/Script/index.js

Comment thread packages/bruno-app/src/components/MonacoEditor/index.js
Comment thread packages/bruno-app/src/utils/monaco/autocomplete.js
Comment on lines +59 to +65
while ((match = VARIABLE_REGEX.exec(text)) !== null) {
const varName = match[1];
const startOffset = match.index;
const endOffset = startOffset + match[0].length;
const startPos = model.getPositionAt(startOffset);
const endPos = model.getPositionAt(endOffset);

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.

⚠️ Potential issue | 🟡 Minor

Variable name isn't trimmed — inconsistent with user expectation.

Both match sites capture [^}]* and use the raw group for scope lookup. {{ foo }} becomes variable name " foo " (with spaces), which won't match any declared variable and gets flagged invalid/red even though CodeMirror tolerates surrounding whitespace in many flows. Trim match[1] before scope resolution to keep behavior consistent across editors.

Also applies to: 123-130

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 59
- 65, The captured variable name from VARIABLE_REGEX is not trimmed, so
whitespace like in "{{ foo }}" yields " foo " and fails scope lookup; update the
handling in variableHighlighting.js to use a trimmed name for resolution by
changing the usage of match[1] to match[1].trim() (e.g., replace const varName =
match[1]; with a trimmed variant) before any scope/variable lookup and
validation, but keep startOffset/endOffset and model.getPositionAt(...) logic
unchanged so highlighting ranges remain correct; apply the same trim fix to the
other occurrence around lines 123-130 as well.

Comment thread packages/bruno-app/src/utils/monaco/variableHighlighting.js
…tip logic

- Updated comment in MonacoEditor to clarify the registration of Bruno API types.
- Added a condition in variableHighlighting to skip tooltips for prompt variables, improving tooltip management.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/components/MonacoEditor/index.js`:
- Around line 103-109: The onDidChangeModelContent listener (registered as
contentDisposable) is firing when the component updates the editor via
editor.setValue(...) and incorrectly invoking onEditRef.current; add a boolean
guard (e.g., isSettingValueRef) to skip calling onEditRef during programmatic
updates: set isSettingValueRef.current = true immediately before
editor.setValue(...) and reset to false after, and update the contentDisposable
callback to return early when isSettingValueRef.current is true; ensure
cachedValueRef updates still occur when syncing external values.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Around line 430-433: The code currently tracks the active tooltip only by
variable name (currentVarName), causing the popup to persist when hovering a
different occurrence of the same variable; update positionAndShowPopup to set a
unique occurrence identifier (e.g., currentVarOccurrence =
`${varName}@${lineNumber}:${startCol}`) and use that identifier in all checks
where currentVarName is compared (replace uses of currentVarName in the
hover/leave logic with currentVarOccurrence), and ensure the old popup is
removed or replaced when the new occurrence id differs so each occurrence is
tracked independently.
- Around line 51-72: The validity check is excluding pathParams because
pathParams is destructured out of getAllVariables into its own variable and
never consulted; update the logic in variableHighlighting.js (around where
getAllVariables, pathParams, and varLookup are defined and where isValid is
computed) so pathParams are considered when determining validity—either merge
pathParams into varLookup after the destructure or change the isValid condition
(used with VARIABLE_REGEX matches, varName, mockDataFunctions, and
pathFoundInVariables) to also check pathFoundInVariables(varName, pathParams);
ensure references to getAllVariables, pathParams, varLookup, VARIABLE_REGEX,
mockDataFunctions, and pathFoundInVariables are used consistently.
- Around line 341-345: The blur handler currently calls
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid))
without handling the returned Promise; modify the editorEl 'blur' listener so
that the Promise returned by updateVariableInScope is handled (either mark the
handler async and await the dispatch, or attach .catch) and surface failures
(e.g., log via console or process logger and optionally revert to rawValue or
show UI feedback). Locate the editorEl.addEventListener('blur', ...) block and
update the call to updateVariableInScope/updateVariableInScope(...) to handle
rejection for varName, newValue, scopeInfo, collection.uid.
🪄 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: b9e4bf4a-b485-4450-be64-fa1228e23e7e

📥 Commits

Reviewing files that changed from the base of the PR and between 671cec3 and 95a3d17.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/MonacoEditor/index.js
  • packages/bruno-app/src/utils/monaco/variableHighlighting.js

Comment on lines +103 to +109
const contentDisposable = editor.onDidChangeModelContent(() => {
const newValue = editor.getValue();
cachedValueRef.current = newValue;
if (onEditRef.current) {
onEditRef.current(newValue);
}
});
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the MonacoEditor file
find . -path "*/packages/bruno-app/src/components/MonacoEditor/index.js" -type f

Repository: usebruno/bruno

Length of output: 115


🏁 Script executed:

# If found, check the file size and read the relevant sections
if [ -f "packages/bruno-app/src/components/MonacoEditor/index.js" ]; then
  wc -l "packages/bruno-app/src/components/MonacoEditor/index.js"
fi

Repository: usebruno/bruno

Length of output: 117


🏁 Script executed:

# Read the file with line numbers to examine lines 103-109 and 183-195
if [ -f "packages/bruno-app/src/components/MonacoEditor/index.js" ]; then
  cat -n "packages/bruno-app/src/components/MonacoEditor/index.js" | sed -n '95,115p'
  echo "---"
  cat -n "packages/bruno-app/src/components/MonacoEditor/index.js" | sed -n '175,200p'
fi

Repository: usebruno/bruno

Length of output: 1783


🏁 Script executed:

# Also check the entire structure around the useEffect hooks to understand the context
if [ -f "packages/bruno-app/src/components/MonacoEditor/index.js" ]; then
  cat -n "packages/bruno-app/src/components/MonacoEditor/index.js" | sed -n '1,50p'
fi

Repository: usebruno/bruno

Length of output: 2021


Suppress onEdit during external value sync.

Line 190 calls editor.setValue(...), which triggers the onDidChangeModelContent listener (lines 103-109). This causes prop-driven reloads to call onEdit and mark data as if the user typed it. Implement the flag-based guard to prevent this.

Proposed fix
   const tooltipCleanupRef = useRef(null);
   const autocompleteCleanupRef = useRef(null);
+  const isSyncingExternalValueRef = useRef(false);
 
@@
     const contentDisposable = editor.onDidChangeModelContent(() => {
       const newValue = editor.getValue();
       cachedValueRef.current = newValue;
-      if (onEditRef.current) {
+      if (!isSyncingExternalValueRef.current && onEditRef.current) {
         onEditRef.current(newValue);
       }
     });
@@
     if (value !== cachedValueRef.current) {
       cachedValueRef.current = value;
       const position = editor.getPosition();
-      editor.setValue(value || '');
+      isSyncingExternalValueRef.current = true;
+      try {
+        editor.setValue(value || '');
+      } finally {
+        isSyncingExternalValueRef.current = false;
+      }
       if (position) {
         editor.setPosition(position);
       }
📝 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.

Suggested change
const contentDisposable = editor.onDidChangeModelContent(() => {
const newValue = editor.getValue();
cachedValueRef.current = newValue;
if (onEditRef.current) {
onEditRef.current(newValue);
}
});
const contentDisposable = editor.onDidChangeModelContent(() => {
const newValue = editor.getValue();
cachedValueRef.current = newValue;
if (!isSyncingExternalValueRef.current && onEditRef.current) {
onEditRef.current(newValue);
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MonacoEditor/index.js` around lines 103 -
109, The onDidChangeModelContent listener (registered as contentDisposable) is
firing when the component updates the editor via editor.setValue(...) and
incorrectly invoking onEditRef.current; add a boolean guard (e.g.,
isSettingValueRef) to skip calling onEditRef during programmatic updates: set
isSettingValueRef.current = true immediately before editor.setValue(...) and
reset to false after, and update the contentDisposable callback to return early
when isSettingValueRef.current is true; ensure cachedValueRef updates still
occur when syncing external values.

Comment on lines +51 to +72
const variables = getAllVariables(collection, item);
const { pathParams = {}, maskedEnvVariables = [], ...varLookup } = variables;
const text = model.getValue();
const newDecorations = [];

let match;
VARIABLE_REGEX.lastIndex = 0;

while ((match = VARIABLE_REGEX.exec(text)) !== null) {
const varName = match[1];
const startOffset = match.index;
const endOffset = startOffset + match[0].length;
const startPos = model.getPositionAt(startOffset);
const endPos = model.getPositionAt(endOffset);

let className;
if (varName.startsWith('?')) {
className = 'bruno-variable-prompt';
} else {
const isMockVariable = varName.startsWith('$') && mockDataFunctions.hasOwnProperty(varName.substring(1));
const isValid = isMockVariable || pathFoundInVariables(varName, varLookup);
className = isValid ? 'bruno-variable-valid' : 'bruno-variable-invalid';
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.

⚠️ Potential issue | 🟠 Major

Include path params in the validity check.

Line 52 removes pathParams from varLookup, but Lines 70-72 never check it, so valid path params can be highlighted as invalid.

Proposed fix
-        const isValid = isMockVariable || pathFoundInVariables(varName, varLookup);
+        const isValid = isMockVariable
+          || pathFoundInVariables(varName, varLookup)
+          || pathFoundInVariables(varName, pathParams);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 51
- 72, The validity check is excluding pathParams because pathParams is
destructured out of getAllVariables into its own variable and never consulted;
update the logic in variableHighlighting.js (around where getAllVariables,
pathParams, and varLookup are defined and where isValid is computed) so
pathParams are considered when determining validity—either merge pathParams into
varLookup after the destructure or change the isValid condition (used with
VARIABLE_REGEX matches, varName, mockDataFunctions, and pathFoundInVariables) to
also check pathFoundInVariables(varName, pathParams); ensure references to
getAllVariables, pathParams, varLookup, VARIABLE_REGEX, mockDataFunctions, and
pathFoundInVariables are used consistently.

Comment on lines +341 to +345
editorEl.addEventListener('blur', () => {
const newValue = editorEl.value;
if (newValue !== rawValue && dispatch) {
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
}
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.

⚠️ Potential issue | 🟡 Minor

Handle failed variable updates.

updateVariableInScope(...) returns a Promise and can reject; leaving the dispatch unhandled can surface unhandled rejections when persistence fails.

Proposed fix
       editorEl.addEventListener('blur', () => {
         const newValue = editorEl.value;
         if (newValue !== rawValue && dispatch) {
-          dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
+          dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid))
+            .catch(() => {
+              editorEl.value = rawValue;
+            });
         }
         editorEl.style.display = 'none';
         valueDisplay.style.display = '';
📝 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.

Suggested change
editorEl.addEventListener('blur', () => {
const newValue = editorEl.value;
if (newValue !== rawValue && dispatch) {
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
}
editorEl.addEventListener('blur', () => {
const newValue = editorEl.value;
if (newValue !== rawValue && dispatch) {
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid))
.catch(() => {
editorEl.value = rawValue;
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 341
- 345, The blur handler currently calls dispatch(updateVariableInScope(varName,
newValue, scopeInfo, collection.uid)) without handling the returned Promise;
modify the editorEl 'blur' listener so that the Promise returned by
updateVariableInScope is handled (either mark the handler async and await the
dispatch, or attach .catch) and surface failures (e.g., log via console or
process logger and optionally revert to rawValue or show UI feedback). Locate
the editorEl.addEventListener('blur', ...) block and update the call to
updateVariableInScope/updateVariableInScope(...) to handle rejection for
varName, newValue, scopeInfo, collection.uid.

Comment on lines +430 to +433
const positionAndShowPopup = (popup, varName, lineNumber, startCol) => {
document.body.appendChild(popup);
activePopup = popup;
currentVarName = varName;
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.

⚠️ Potential issue | 🟡 Minor

Track the active tooltip by occurrence, not only name.

When the same variable appears multiple times, Lines 506-510 keep the old popup because currentVarName matches, even if the hover moved to a different occurrence.

Proposed fix
-  let currentVarName = null;
+  let currentVarKey = null;
@@
-    currentVarName = varName;
+    currentVarKey = `${lineNumber}:${startCol}:${varName}`;
@@
-      currentVarName = null;
+      currentVarKey = null;
@@
       // Same variable — keep current popup
-      if (activePopup && currentVarName === varInfo.varName) {
+      const varKey = `${varInfo.lineNumber}:${varInfo.startCol}:${varInfo.varName}`;
+      if (activePopup && currentVarKey === varKey) {
         clearTimeout(hideTimeout);
         return;
       }

Also applies to: 506-510

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 430
- 433, The code currently tracks the active tooltip only by variable name
(currentVarName), causing the popup to persist when hovering a different
occurrence of the same variable; update positionAndShowPopup to set a unique
occurrence identifier (e.g., currentVarOccurrence =
`${varName}@${lineNumber}:${startCol}`) and use that identifier in all checks
where currentVarName is compared (replace uses of currentVarName in the
hover/leave logic with currentVarOccurrence), and ensure the old popup is
removed or replaced when the new occurrence id differs so each occurrence is
tracked independently.

- Changed variable handling to use let for rawValue, interpolatedValue, and displayValue for better clarity and mutability.
- Updated tooltip display logic to ensure values are correctly updated when changes occur in the editor.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
packages/bruno-app/src/components/MonacoEditor/index.js (1)

112-119: ⚠️ Potential issue | 🟠 Major

Suppress onEdit during external value sync.

model.pushEditOperations(...) still emits a model-content change, so prop-driven reloads can call onEdit and mark the request dirty as if the user typed.

Proposed fix
   const variableCleanupRef = useRef(null);
   const tooltipCleanupRef = useRef(null);
   const autocompleteCleanupRef = useRef(null);
+  const isSyncingExternalValueRef = useRef(false);
...
     const contentDisposable = editor.onDidChangeModelContent(() => {
       const newValue = editor.getValue();
       cachedValueRef.current = newValue;
+      if (isSyncingExternalValueRef.current) return;
       if (onEditRef.current) {
         onEditRef.current(newValue);
       }
     });
...
-      model.pushEditOperations(
-        selections,
-        [{ range: fullRange, text: value || '' }],
-        () => selections
-      );
+      isSyncingExternalValueRef.current = true;
+      try {
+        model.pushEditOperations(
+          selections,
+          [{ range: fullRange, text: value || '' }],
+          () => selections
+        );
+      } finally {
+        isSyncingExternalValueRef.current = false;
+      }
#!/bin/bash
# Verify the MonacoEditor value-sync path has an explicit guard that prevents
# programmatic model edits from invoking onEdit.
sed -n '30,230p' packages/bruno-app/src/components/MonacoEditor/index.js \
  | rg -n -C3 'isSyncingExternalValueRef|onDidChangeModelContent|pushEditOperations|onEditRef'

Also applies to: 209-229

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/components/MonacoEditor/index.js` around lines 112 -
119, The model change handler currently calls onEditRef whenever
editor.onDidChangeModelContent fires, which includes programmatic edits from
model.pushEditOperations; add and use a boolean guard (e.g.,
isSyncingExternalValueRef) around any programmatic updates so that before
performing pushEditOperations you set isSyncingExternalValueRef = true and clear
it immediately after, and update the onDidChangeModelContent callback to skip
invoking onEditRef (and avoid updating cachedValueRef or marking dirty) when
isSyncingExternalValueRef is true; reference the existing symbols
editor.onDidChangeModelContent, model.pushEditOperations, onEditRef,
cachedValueRef and the new isSyncingExternalValueRef flag to implement this
guard.
packages/bruno-app/src/utils/monaco/variableHighlighting.js (3)

436-439: ⚠️ Potential issue | 🟡 Minor

Track tooltip identity by occurrence, not only variable name.

Moving between two occurrences of the same variable keeps the old popup because currentVarName matches. Include the line/column in the active key.

Proposed fix
-  let currentVarName = null;
+  let currentVarKey = null;
...
-    currentVarName = varName;
+    currentVarKey = `${lineNumber}:${startCol}:${varName}`;
...
-      currentVarName = null;
+      currentVarKey = null;
...
-      if (activePopup && currentVarName === varInfo.varName) {
+      const varKey = `${varInfo.lineNumber}:${varInfo.startCol}:${varInfo.varName}`;
+      if (activePopup && currentVarKey === varKey) {
         clearTimeout(hideTimeout);
         return;
       }

Also applies to: 512-517

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 436
- 439, The popup identity is currently tracked only by currentVarName so moving
between occurrences of the same variable keeps the old popup; update the
tracking to include occurrence position (line and column) — in
positionAndShowPopup build a unique key (e.g.,
`${varName}:${lineNumber}:${startCol}`) and set that to a new tracker (replace
currentVarName with currentVarKey or add currentPopupKey), use that key wherever
you compare/clear popups (including the logic referenced around lines 512-517)
so popups are considered different for different occurrences even if the
variable name is the same.

343-350: ⚠️ Potential issue | 🟡 Minor

Handle failed inline variable saves.

dispatch(updateVariableInScope(...)) can reject, but the tooltip immediately mutates rawValue / displayValue, leaving stale UI and an unhandled rejection on persistence failure.

Proposed fix
-      editorEl.addEventListener('blur', () => {
+      editorEl.addEventListener('blur', async () => {
         const newValue = editorEl.value;
         if (newValue !== rawValue && dispatch) {
-          dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
-          rawValue = newValue;
-          interpolatedValue = getInterpolatedValue(rawValue);
-          displayValue = interpolatedValue;
-          updateValueDisplay();
+          try {
+            await dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
+            rawValue = newValue;
+            interpolatedValue = getInterpolatedValue(rawValue);
+            displayValue = interpolatedValue;
+            updateValueDisplay();
+          } catch {
+            editorEl.value = rawValue;
+          }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 343
- 350, The blur handler currently calls dispatch(updateVariableInScope(...)) and
immediately mutates rawValue/interpolatedValue/displayValue and calls
updateValueDisplay(), causing stale UI and unhandled rejections if persistence
fails; change the blur handler (editorEl.addEventListener('blur')) to treat
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid)) as
an async operation: wait for the promise (via await or .then/.catch), only
update rawValue, interpolatedValue (via getInterpolatedValue) and call
updateValueDisplay() on success, and on failure revert the input/display to the
previous rawValue (and call updateValueDisplay()) and handle/log the error to
avoid unhandled rejection.

52-73: ⚠️ Potential issue | 🟠 Major

Normalize names and include path params in validation.

{{ foo }} still resolves as " foo ", and valid pathParams are still excluded from isValid, so both can be highlighted as invalid incorrectly.

Proposed fix
-      const varName = match[1];
+      const varName = match[1].trim();
...
-        const isValid = isMockVariable || pathFoundInVariables(varName, varLookup);
+        const isValid = isMockVariable
+          || pathFoundInVariables(varName, varLookup)
+          || pathFoundInVariables(varName, pathParams);
...
-        return { varName: match[1], startCol, endCol, lineNumber: position.lineNumber };
+        return { varName: match[1].trim(), startCol, endCol, lineNumber: position.lineNumber };

Also applies to: 125-132

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 52
- 73, The VARIABLE_REGEX match yields varName with possible surrounding
whitespace and pathParams were left out of the validation lookup, causing valid
variables like "{{ foo }}" and pathParams to be marked invalid; to fix, trim the
matched varName before tests (use a trimmedVarName variable) and use that
trimmed name for prompt ('?') and mock ('$') checks and when calling
pathFoundInVariables, and include pathParams in the validation lookup (or pass
an object that merges pathParams into varLookup) so path parameters are
considered valid; update references around VARIABLE_REGEX.exec loop, varName
usage, isMockVariable (mockDataFunctions), and the pathFoundInVariables call
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/bruno-app/src/components/MonacoEditor/index.js`:
- Around line 112-119: The model change handler currently calls onEditRef
whenever editor.onDidChangeModelContent fires, which includes programmatic edits
from model.pushEditOperations; add and use a boolean guard (e.g.,
isSyncingExternalValueRef) around any programmatic updates so that before
performing pushEditOperations you set isSyncingExternalValueRef = true and clear
it immediately after, and update the onDidChangeModelContent callback to skip
invoking onEditRef (and avoid updating cachedValueRef or marking dirty) when
isSyncingExternalValueRef is true; reference the existing symbols
editor.onDidChangeModelContent, model.pushEditOperations, onEditRef,
cachedValueRef and the new isSyncingExternalValueRef flag to implement this
guard.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Around line 436-439: The popup identity is currently tracked only by
currentVarName so moving between occurrences of the same variable keeps the old
popup; update the tracking to include occurrence position (line and column) — in
positionAndShowPopup build a unique key (e.g.,
`${varName}:${lineNumber}:${startCol}`) and set that to a new tracker (replace
currentVarName with currentVarKey or add currentPopupKey), use that key wherever
you compare/clear popups (including the logic referenced around lines 512-517)
so popups are considered different for different occurrences even if the
variable name is the same.
- Around line 343-350: The blur handler currently calls
dispatch(updateVariableInScope(...)) and immediately mutates
rawValue/interpolatedValue/displayValue and calls updateValueDisplay(), causing
stale UI and unhandled rejections if persistence fails; change the blur handler
(editorEl.addEventListener('blur')) to treat
dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid)) as
an async operation: wait for the promise (via await or .then/.catch), only
update rawValue, interpolatedValue (via getInterpolatedValue) and call
updateValueDisplay() on success, and on failure revert the input/display to the
previous rawValue (and call updateValueDisplay()) and handle/log the error to
avoid unhandled rejection.
- Around line 52-73: The VARIABLE_REGEX match yields varName with possible
surrounding whitespace and pathParams were left out of the validation lookup,
causing valid variables like "{{ foo }}" and pathParams to be marked invalid; to
fix, trim the matched varName before tests (use a trimmedVarName variable) and
use that trimmed name for prompt ('?') and mock ('$') checks and when calling
pathFoundInVariables, and include pathParams in the validation lookup (or pass
an object that merges pathParams into varLookup) so path parameters are
considered valid; update references around VARIABLE_REGEX.exec loop, varName
usage, isMockVariable (mockDataFunctions), and the pathFoundInVariables call
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ee61ea2e-0e96-47af-b971-00580acac639

📥 Commits

Reviewing files that changed from the base of the PR and between 95a3d17 and da491de.

📒 Files selected for processing (8)
  • packages/bruno-app/src/components/MonacoEditor/index.js
  • packages/bruno-app/src/components/MonacoEditor/index.spec.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/ScriptEditor/index.js
  • packages/bruno-app/src/components/ScriptEditor/index.spec.js
  • packages/bruno-app/src/utils/monaco/autocomplete.js
  • packages/bruno-app/src/utils/monaco/brunoTheme.js
  • packages/bruno-app/src/utils/monaco/variableHighlighting.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/bruno-app/src/components/ScriptEditor/index.js
  • packages/bruno-app/src/utils/monaco/autocomplete.js
  • packages/bruno-app/src/components/MonacoEditor/index.spec.js
  • packages/bruno-app/src/utils/monaco/brunoTheme.js

…bal styles

- Introduced a new ScriptEditor component to replace CodeEditor for improved scripting capabilities.
- Updated global styles to include specific styles for Monaco tooltip textarea editor.
- Refactored variable tooltip logic to utilize shared styles, enhancing consistency across the application.
- Improved autocomplete functionality by ensuring proper context resolution for variable hints in the Monaco Editor.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
packages/bruno-app/src/utils/monaco/variableHighlighting.js (3)

26-26: ⚠️ Potential issue | 🟠 Major

Resolve path params and normalize captured variable names.

pathParams are extracted but not used, while detectScope() never returns the existing pathParam scope. This can mark valid path params as invalid and show the tooltip as undefined/editable. Also trim match[1] before lookup so {{ foo }} resolves like {{foo}}.

Proposed fix
-const READ_ONLY_SCOPES = new Set(['process.env', 'runtime', 'dynamic', 'oauth2', 'undefined']);
+const READ_ONLY_SCOPES = new Set(['process.env', 'runtime', 'dynamic', 'oauth2', 'undefined', 'pathParam']);
@@
-      const varName = match[1];
+      const varName = match[1].trim();
@@
-        const isValid = isMockVariable || pathFoundInVariables(varName, varLookup);
+        const isValid = isMockVariable
+          || pathFoundInVariables(varName, varLookup)
+          || pathFoundInVariables(varName, pathParams);
@@
-        return { varName: match[1], startCol, endCol, lineNumber: position.lineNumber };
+        return { varName: match[1].trim(), startCol, endCol, lineNumber: position.lineNumber };
@@
     if (varName.startsWith('process.env.')) {
       const envKey = varName.replace('process.env.', '');
       const variables = getAllVariables(collection, item);
       const value = get(variables, varName);
       return { type: 'process.env', value, data: { envKey } };
     }
+
+    const variables = getAllVariables(collection, item);
+    const pathParamValue = get(variables.pathParams, varName);
+    if (pathParamValue !== undefined) {
+      return { type: 'pathParam', value: pathParamValue, data: {} };
+    }
 
     const scopeInfo = getVariableScope(varName, collection, item);

Also applies to: 52-73, 121-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` at line 26, The
code currently ignores extracted pathParams and doesn't normalize captured
variable names, so update the logic to trim whitespace from the captured name
(e.g., use match[1].trim()) before any lookup and use the extracted pathParams
map to resolve scope: modify detectScope() to check the trimmed key against
pathParams and return the appropriate 'pathParam' scope when present, and ensure
all places that call detectScope()/lookup (the blocks around READ_ONLY_SCOPES
and the sections referenced in the comment) use the trimmed name and pathParams
resolution so valid path params are treated as read-only/non-editable and
tooltips show the correct value.

330-338: ⚠️ Potential issue | 🟡 Minor

Handle failed inline variable updates.

The tooltip updates local UI immediately, but the dispatched async update can reject. Catch failures so the tooltip does not silently show a value that failed to persist.

Proposed fix
-          dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid));
-          rawValue = newValue;
-          interpolatedValue = getInterpolatedValue(rawValue);
-          displayValue = interpolatedValue;
-          updateValueDisplay();
+          dispatch(updateVariableInScope(varName, newValue, scopeInfo, collection.uid))
+            .then(() => {
+              rawValue = newValue;
+              interpolatedValue = getInterpolatedValue(rawValue);
+              displayValue = interpolatedValue;
+              updateValueDisplay();
+            })
+            .catch(() => {
+              editorEl.value = rawValue;
+              updateValueDisplay();
+            });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 330
- 338, The inline update currently updates the tooltip UI immediately inside the
editorEl.blur handler but doesn't handle a rejected async update; capture the
previous value (e.g., prevValue = rawValue) before calling
dispatch(updateVariableInScope(...)), call Promise.resolve(dispatch(...)) and on
success keep the new state, but on failure revert
rawValue/interpolatedValue/displayValue back to prevValue (recompute
interpolatedValue via getInterpolatedValue(prevValue)) and call
updateValueDisplay(), and optionally surface an error (e.g., via a passed error
dispatcher) so the tooltip doesn't silently show a value that failed to persist;
apply this change in the editorEl.addEventListener('blur', ...) block that
invokes updateVariableInScope and updateValueDisplay.

423-426: ⚠️ Potential issue | 🟡 Minor

Track tooltip identity by occurrence, not only variable name.

Hovering another {{var}} occurrence with the same name keeps the old popup because currentVarName still matches. Include line/column in the active key.

Proposed fix
-  let currentVarName = null;
+  let currentVarKey = null;
@@
-    currentVarName = varName;
+    currentVarKey = `${lineNumber}:${startCol}:${varName}`;
@@
-      currentVarName = null;
+      currentVarKey = null;
@@
-      if (activePopup && currentVarName === varInfo.varName) {
+      const varKey = `${varInfo.lineNumber}:${varInfo.startCol}:${varInfo.varName}`;
+      if (activePopup && currentVarKey === varKey) {
         clearTimeout(hideTimeout);
         return;
       }

Also applies to: 499-504

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 423
- 426, The popup identity is only tracked by currentVarName so popups for the
same variable name at different locations are reused; update the tracking to
include occurrence (line and column): in positionAndShowPopup create and set an
active key like activeVarKey = `${varName}:${lineNumber}:${startCol}` (or
similar) instead of only currentVarName, use that key wherever currentVarName is
currently checked (e.g., the code paths that decide to reuse/hide popups), and
ensure hide/compare logic (the other popup-handling functions referenced in the
diff) also uses activeVarKey so each {{var}} occurrence is treated uniquely.
🤖 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/monaco/variableHighlighting.js`:
- Around line 376-389: The click handler on copyBtn currently calls
navigator.clipboard.writeText(...) without handling rejection; update the
handler in the copyBtn.addEventListener callback to append a .catch(...) to
navigator.clipboard.writeText(textToCopy) to handle failures (use copyBtn,
copyTimeout, scopeInfo, interpolatedValue, CHECK_ICON, COPY_ICON,
COPY_SUCCESS_TIMEOUT to locate the code) — in the .catch block revert any UI
changes (ensure copyBtn.innerHTML and class state are consistent), optionally
show an error state/message or console.error, and clear/reset copyTimeout so a
subsequent click can retry.

---

Duplicate comments:
In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js`:
- Line 26: The code currently ignores extracted pathParams and doesn't normalize
captured variable names, so update the logic to trim whitespace from the
captured name (e.g., use match[1].trim()) before any lookup and use the
extracted pathParams map to resolve scope: modify detectScope() to check the
trimmed key against pathParams and return the appropriate 'pathParam' scope when
present, and ensure all places that call detectScope()/lookup (the blocks around
READ_ONLY_SCOPES and the sections referenced in the comment) use the trimmed
name and pathParams resolution so valid path params are treated as
read-only/non-editable and tooltips show the correct value.
- Around line 330-338: The inline update currently updates the tooltip UI
immediately inside the editorEl.blur handler but doesn't handle a rejected async
update; capture the previous value (e.g., prevValue = rawValue) before calling
dispatch(updateVariableInScope(...)), call Promise.resolve(dispatch(...)) and on
success keep the new state, but on failure revert
rawValue/interpolatedValue/displayValue back to prevValue (recompute
interpolatedValue via getInterpolatedValue(prevValue)) and call
updateValueDisplay(), and optionally surface an error (e.g., via a passed error
dispatcher) so the tooltip doesn't silently show a value that failed to persist;
apply this change in the editorEl.addEventListener('blur', ...) block that
invokes updateVariableInScope and updateValueDisplay.
- Around line 423-426: The popup identity is only tracked by currentVarName so
popups for the same variable name at different locations are reused; update the
tracking to include occurrence (line and column): in positionAndShowPopup create
and set an active key like activeVarKey = `${varName}:${lineNumber}:${startCol}`
(or similar) instead of only currentVarName, use that key wherever
currentVarName is currently checked (e.g., the code paths that decide to
reuse/hide popups), and ensure hide/compare logic (the other popup-handling
functions referenced in the diff) also uses activeVarKey so each {{var}}
occurrence is treated uniquely.
🪄 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: 8d69cafa-b63a-43cc-b3f6-9e2d2f7eac77

📥 Commits

Reviewing files that changed from the base of the PR and between da491de and 47d52a5.

📒 Files selected for processing (7)
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
  • packages/bruno-app/src/components/ScriptEditor/index.js
  • packages/bruno-app/src/globalStyles.js
  • packages/bruno-app/src/utils/monaco/autocomplete.js
  • packages/bruno-app/src/utils/monaco/brunoApiTypes.js
  • packages/bruno-app/src/utils/monaco/brunoTheme.js
  • packages/bruno-app/src/utils/monaco/variableHighlighting.js
✅ Files skipped from review due to trivial changes (2)
  • packages/bruno-app/src/globalStyles.js
  • packages/bruno-app/src/components/RequestPane/RequestBody/index.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/bruno-app/src/utils/monaco/brunoApiTypes.js
  • packages/bruno-app/src/utils/monaco/brunoTheme.js

Comment on lines +376 to +389
copyBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (copyTimeout) return;
const textToCopy = typeof scopeInfo.value === 'object'
? JSON.stringify(scopeInfo.value) : interpolatedValue;
navigator.clipboard.writeText(textToCopy).then(() => {
copyBtn.innerHTML = CHECK_ICON;
copyBtn.classList.add('copy-success');
copyTimeout = setTimeout(() => {
copyBtn.innerHTML = COPY_ICON;
copyBtn.classList.remove('copy-success');
copyTimeout = null;
}, COPY_SUCCESS_TIMEOUT);
});
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.

⚠️ Potential issue | 🟡 Minor

Handle clipboard write failures.

navigator.clipboard.writeText() can reject; add a .catch() path so copy failures do not surface as unhandled promise rejections.

Proposed fix
-        navigator.clipboard.writeText(textToCopy).then(() => {
-          copyBtn.innerHTML = CHECK_ICON;
-          copyBtn.classList.add('copy-success');
-          copyTimeout = setTimeout(() => {
-            copyBtn.innerHTML = COPY_ICON;
-            copyBtn.classList.remove('copy-success');
-            copyTimeout = null;
-          }, COPY_SUCCESS_TIMEOUT);
-        });
+        navigator.clipboard.writeText(textToCopy)
+          .then(() => {
+            copyBtn.innerHTML = CHECK_ICON;
+            copyBtn.classList.add('copy-success');
+            copyTimeout = setTimeout(() => {
+              copyBtn.innerHTML = COPY_ICON;
+              copyBtn.classList.remove('copy-success');
+              copyTimeout = null;
+            }, COPY_SUCCESS_TIMEOUT);
+          })
+          .catch(() => {
+            copyTimeout = null;
+          });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bruno-app/src/utils/monaco/variableHighlighting.js` around lines 376
- 389, The click handler on copyBtn currently calls
navigator.clipboard.writeText(...) without handling rejection; update the
handler in the copyBtn.addEventListener callback to append a .catch(...) to
navigator.clipboard.writeText(textToCopy) to handle failures (use copyBtn,
copyTimeout, scopeInfo, interpolatedValue, CHECK_ICON, COPY_ICON,
COPY_SUCCESS_TIMEOUT to locate the code) — in the .catch block revert any UI
changes (ensure copyBtn.innerHTML and class state are consistent), optionally
show an error state/message or console.error, and clear/reset copyTimeout so a
subsequent click can retry.

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.

1 participant