Skip to content

Conversation

@sanish-bruno
Copy link
Collaborator

@sanish-bruno sanish-bruno commented Jan 30, 2026

Description

Jira
#6430
GH discussion #6996

Screen.Recording.2026-01-31.at.1.54.36.AM.mov

Contribution Checklist:

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

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

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added enable/disable toggle for individual gRPC messages with checkbox UI
    • New send message dropdown menu with options to send first enabled message or all enabled messages
    • Connect and Connect & Send buttons for client-streaming and bidi-streaming scenarios
    • Atomic connect-and-send operation for gRPC requests
  • UI/UX Improvements

    • Visual disabled state styling for message containers

✏️ Tip: You can customize this high-level summary in your review settings.

- Introduced a checkbox to toggle the enabled state of individual gRPC messages.
- Updated the Redux store to manage the enabled state of messages.
- Enhanced the UI to reflect the enabled/disabled status visually.
- Implemented functionality to send all enabled messages in one atomic operation for client-streaming methods.
- Updated related components and styles to accommodate the new features.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 30, 2026

Walkthrough

This PR introduces enable/disable toggles for individual gRPC messages and adds "connect & send" atomic operations for client-streaming and bidi-streaming gRPC methods, allowing messages to be sent immediately upon connection.

Changes

Cohort / File(s) Summary
gRPC Body Message Management
packages/bruno-app/src/components/RequestPane/GrpcBody/StyledWrapper.js, packages/bruno-app/src/components/RequestPane/GrpcBody/index.js
Added UI checkbox to toggle individual gRPC message enabled state, disabled styling for message containers, and propagation of enabled flag through message editing, validation, and creation workflows.
gRPC Query URL & Streaming Controls
packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/StyledWrapper.js, packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js
Introduced send-message dropdown UI for client/bidi-streaming scenarios with Connect, Connect & Send, and message-sending options; restructured conditional rendering for streaming method types and added handlers for atomic connect-and-send operations.
State Management & Actions
packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js, packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
Added toggleGrpcMessageEnabled reducer to toggle message enabled state; branched sendRequest logic to route client/bidi-streaming requests through new connectAndSendGrpc path.
Network Utilities
packages/bruno-app/src/utils/network/index.js
Added connectAndSendGrpc function for atomic connect-and-send operations and sendGrpcMessagesSequentially function to send multiple enabled messages with aggregated result tracking.
Electron IPC Handler
packages/bruno-electron/src/ipc/network/grpc-event-handlers.js
Added grpc:connect-and-send IPC handler implementing atomic connection, credential setup, and sequential message dispatch in a single operation with error handling.
Schema, Types & Parsing
packages/bruno-schema-types/src/requests/grpc.ts, packages/bruno-schema/src/collections/index.js, packages/bruno-lang/v2/src/bruToJson.js, packages/bruno-lang/v2/src/jsonToBru.js
Added optional enabled field to GrpcMessage type and schema with true default; extended bru file parser to extract and emit enabled flag; updated serializer to conditionally emit enabled: false.
Test Coverage
packages/bruno-lang/v2/tests/bruToJson.spec.js, packages/bruno-lang/v2/tests/jsonToBru.spec.js, tests/grpc/make-request/make-request.spec.ts, tests/utils/page/locators.ts
Updated test expectations to include enabled property on gRPC messages; added E2E tests for atomic connect & send workflows; introduced locators for new UI controls (connect, connect & send, send dropdown options).

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as GrpcQueryUrl UI
    participant Redux as Redux Store
    participant Network as Network Utils
    participant IPC as Electron IPC
    participant GRPC as gRPC Handler
    
    User->>UI: Click "Connect & Send"
    UI->>Redux: dispatch(sendRequest)
    Redux->>Network: connectAndSendGrpc(item, collection, env)
    Network->>IPC: invoke('grpc:connect-and-send', payload)
    IPC->>GRPC: grpc:connect-and-send handler
    GRPC->>GRPC: Prepare & validate request
    GRPC->>GRPC: Filter enabled messages
    GRPC->>GRPC: Configure TLS/certs
    GRPC->>GRPC: Start connection
    GRPC->>GRPC: Send all enabled messages<br/>(sequentially)
    GRPC->>GRPC: End stream
    GRPC-->>IPC: Return success payload
    IPC-->>Network: Resolve with result
    Network-->>Redux: Return aggregated result
    Redux-->>UI: Update state
    UI-->>User: Display response & message count
Loading
sequenceDiagram
    actor User
    participant UI as GrpcBody UI
    participant Redux as Redux Store
    
    User->>UI: Click message checkbox
    UI->>Redux: dispatch(toggleGrpcMessageEnabled)
    Redux->>Redux: Toggle enabled flag<br/>in message object
    Redux-->>UI: Update draft state
    UI-->>User: Checkbox reflects new state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • usebruno/bruno#6568: Modifies overlapping gRPC UI components (GrpcQueryUrl, StyledWrappers) and gRPC icon/theming integration.

Suggested labels

size/XXL

Suggested reviewers

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

Poem

📨 Messages now toggle with checkboxes bright,
Connect and send in atomic flight,
Streaming flows dance left and right,
gRPC requests, now polished just right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature: adding 'connect & send' functionality for gRPC, which is the central theme across all changed files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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: 6

🤖 Fix all issues with AI agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js`:
- Around line 214-227: The disabled styling is applied unconditionally via the
message-container class using the enabled prop, which leaves messages visually
disabled when the method switches to single-message modes; update the condition
so the 'disabled' class is only added when messages can actually be toggled
(e.g., guard with canClientSendMultipleMessages) or alternatively auto-enable
the message when canClientSendMultipleMessages becomes false; adjust the
className expression where MessageToolbar and the message container are rendered
(references: message-container, isSingleMessage, isLast, enabled,
canClientSendMultipleMessages, MessageToolbar) to either remove 'disabled'
unless canClientSendMultipleMessages is true or call onToggleEnabled(true) when
canClientSendMultipleMessages flips to false.
- Around line 20-46: The checkbox in the MessageToolbar component lacks an
accessible label; add an id to the label span (e.g., message-label span ->
id={`grpc-message-label-${index}`}) and reference that id from the checkbox via
aria-labelledby (on the input element use
aria-labelledby={`grpc-message-label-${index}`}) so screen readers associate the
checkbox with the "Message {index + 1}" text; update elements inside
MessageToolbar (the input and the span with className "message-label")
accordingly and keep the existing data-testid and checked/onChange props.

In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js`:
- Around line 327-342: The dropdown-close effect currently only tracks
showSendMessageDropdown, so if isConnectionActive becomes false the menu can
remain open and its listener stays attached; update the effect (or add a small
effect) to also depend on isConnectionActive and when isConnectionActive is
false call setShowSendMessageDropdown(false) and ensure the existing cleanup
still removes the mousedown listener (keep sendMessageDropdownRef,
setShowSendMessageDropdown, showSendMessageDropdown and the handleClickOutside
logic intact).
- Around line 483-542: The connection/send controls use non-focusable <div>s;
update the interactive elements tied to handleCancelConnection,
handleEndConnection, the send-dropdown trigger (sendMessageDropdownRef /
showSendMessageDropdown), handleSendFirstMessage, and handleSendAllMessages so
they are keyboard-accessible: replace those clickable <div> wrappers with
<button type="button"> elements (preferred) or add role="button", tabIndex={0},
and onKeyDown handlers that call the same handlers on Enter/Space; also ensure
the "Send All" control uses the disabled attribute when enabledMessages.length
=== 0 (instead of only a disabled class) and prevent its click/keyboard handlers
when disabled. Ensure aria-label or visible text remains for screen readers.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`:
- Around line 572-589: The promise path for client-streaming/bidi-streaming uses
connectAndSendGrpc(...) but its catch only toasts the error, leaving the outer
promise unresolved; update the catch to call the promise reject (same as resolve
path) so callers aren't left pending—i.e., in the catch handler for
connectAndSendGrpc(itemCopy, collectionCopy, environment,
collectionCopy.runtimeVariables) invoke reject(err) after/to accompany
toast.error(err.message); similarly ensure sendGrpcRequest(...) catch also
rejects if it should mirror behavior; locate these by the connectAndSendGrpc and
sendGrpcRequest calls inside the sendRequest/sendGrpc action and add reject(err)
in their .catch handlers.

In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js`:
- Around line 1646-1666: The reducer toggleGrpcMessageEnabled reads
item.draft.request.body.grpc without ensuring the request body contains a gRPC
messages array; update the guard inside toggleGrpcMessageEnabled (after
isItemARequest and after creating item.draft) to verify item.draft.request and
that item.draft.request.body.grpc is an array (e.g., const messages =
item.draft.request.body.grpc; proceed only if Array.isArray(messages) and
messages[messageIndex] !== undefined) before accessing or toggling
messages[messageIndex].enabled to prevent runtime errors for non‑gRPC requests.
🧹 Nitpick comments (2)
packages/bruno-app/src/utils/network/index.js (1)

145-172: LGTM with a minor note.

The sequential send logic is sound. Using m.enabled !== false correctly treats undefined and true as enabled. The continue-on-error behavior is appropriate for batch operations.

One consideration: if a single sendGrpcMessage hangs indefinitely, this function will block. If the IPC layer doesn't have built-in timeouts, you may want to add a per-message timeout wrapper in the future for production resilience.

packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js (1)

267-325: Add brief JSDoc for the new gRPC action handlers.

These handlers encapsulate connection and send side‑effects; a short JSDoc will make expectations and error behavior easier to maintain.

As per coding guidelines, Add JSDoc comments to abstractions for additional details.

Comment on lines 20 to +46
const MessageToolbar = ({
index,
enabled,
canClientStream,
isConnectionActive,
onSend,
onToggleEnabled,
onRegenerateMessage,
onPrettify,
onDeleteMessage,
showDelete
showDelete,
showCheckbox
}) => {
return (
<div className="message-toolbar">
<span className="message-label">Message {index + 1}</span>
<div className="message-label-container">
{showCheckbox && (
<input
type="checkbox"
checked={enabled}
onChange={onToggleEnabled}
className="message-checkbox"
data-testid={`grpc-message-enabled-${index}`}
/>
)}
<span className="message-label">Message {index + 1}</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd "index.js" packages/bruno-app/src/components/RequestPane/GrpcBody

Repository: usebruno/bruno

Length of output: 121


🏁 Script executed:

cat -n packages/bruno-app/src/components/RequestPane/GrpcBody/index.js | head -60

Repository: usebruno/bruno

Length of output: 2652


🏁 Script executed:

rg "aria-labelledby|htmlFor" packages/bruno-app/src/components --type js --type jsx --type tsx -A 2 -B 2 | head -50

Repository: usebruno/bruno

Length of output: 85


🏁 Script executed:

rg "aria-label" packages/bruno-app/src/components --type js --type jsx --type tsx -A 1 -B 1 | head -30

Repository: usebruno/bruno

Length of output: 85


🏁 Script executed:

rg "aria-labelledby|htmlFor|aria-label" packages/bruno-app/src/components -A 2 -B 2 | head -80

Repository: usebruno/bruno

Length of output: 8085


🏁 Script executed:

cat -n packages/bruno-app/src/components/RequestPane/GrpcBody/index.js | sed -n '33,50p'

Repository: usebruno/bruno

Length of output: 861


Add accessible label for the message checkbox.

The checkbox has no associated label for screen readers. Connect it to the adjacent "Message {index + 1}" text using aria-labelledby:

Suggested fix
         {showCheckbox && (
           <input
             type="checkbox"
             checked={enabled}
             onChange={onToggleEnabled}
             className="message-checkbox"
             data-testid={`grpc-message-enabled-${index}`}
+            aria-labelledby={`grpc-message-label-${index}`}
           />
         )}
-        <span className="message-label">Message {index + 1}</span>
+        <span id={`grpc-message-label-${index}`} className="message-label">Message {index + 1}</span>
📝 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 MessageToolbar = ({
index,
enabled,
canClientStream,
isConnectionActive,
onSend,
onToggleEnabled,
onRegenerateMessage,
onPrettify,
onDeleteMessage,
showDelete
showDelete,
showCheckbox
}) => {
return (
<div className="message-toolbar">
<span className="message-label">Message {index + 1}</span>
<div className="message-label-container">
{showCheckbox && (
<input
type="checkbox"
checked={enabled}
onChange={onToggleEnabled}
className="message-checkbox"
data-testid={`grpc-message-enabled-${index}`}
/>
)}
<span className="message-label">Message {index + 1}</span>
</div>
const MessageToolbar = ({
index,
enabled,
canClientStream,
isConnectionActive,
onSend,
onToggleEnabled,
onRegenerateMessage,
onPrettify,
onDeleteMessage,
showDelete,
showCheckbox
}) => {
return (
<div className="message-toolbar">
<div className="message-label-container">
{showCheckbox && (
<input
type="checkbox"
checked={enabled}
onChange={onToggleEnabled}
className="message-checkbox"
data-testid={`grpc-message-enabled-${index}`}
aria-labelledby={`grpc-message-label-${index}`}
/>
)}
<span id={`grpc-message-label-${index}`} className="message-label">Message {index + 1}</span>
</div>
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js` around lines
20 - 46, The checkbox in the MessageToolbar component lacks an accessible label;
add an id to the label span (e.g., message-label span ->
id={`grpc-message-label-${index}`}) and reference that id from the checkbox via
aria-labelledby (on the input element use
aria-labelledby={`grpc-message-label-${index}`}) so screen readers associate the
checkbox with the "Message {index + 1}" text; update elements inside
MessageToolbar (the input and the span with className "message-label")
accordingly and keep the existing data-testid and checked/onChange props.
Comment on lines +214 to 227
<div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${!enabled ? 'disabled' : ''}`}>
<MessageToolbar
index={index}
enabled={enabled}
canClientStream={canClientStream}
isConnectionActive={isConnectionActive}
onSend={onSend}
onToggleEnabled={onToggleEnabled}
onRegenerateMessage={onRegenerateMessage}
onPrettify={onPrettify}
onDeleteMessage={onDeleteMessage}
showDelete={index > 0}
showCheckbox={canClientSendMultipleMessages}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid stranded disabled state when switching to single‑message methods.

If a message is disabled in streaming mode and the method switches to unary/server‑streaming, the checkbox disappears but the message stays styled disabled. Consider gating the disabled styling to multi‑message modes (or auto‑enabling on method change) so users aren’t stuck.

🔧 Suggested guard
-    <div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${!enabled ? 'disabled' : ''}`}>
+    <div className={`message-container ${isSingleMessage ? 'single' : ''} ${isLast ? 'last' : ''} ${canClientSendMultipleMessages && !enabled ? 'disabled' : ''}`}>
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcBody/index.js` around lines
214 - 227, The disabled styling is applied unconditionally via the
message-container class using the enabled prop, which leaves messages visually
disabled when the method switches to single-message modes; update the condition
so the 'disabled' class is only added when messages can actually be toggled
(e.g., guard with canClientSendMultipleMessages) or alternatively auto-enable
the message when canClientSendMultipleMessages becomes false; adjust the
className expression where MessageToolbar and the message container are rendered
(references: message-container, isSingleMessage, isLast, enabled,
canClientSendMultipleMessages, MessageToolbar) to either remove 'disabled'
unless canClientSendMultipleMessages is true or call onToggleEnabled(true) when
canClientSendMultipleMessages flips to false.
Comment on lines +327 to +342
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (sendMessageDropdownRef.current && !sendMessageDropdownRef.current.contains(event.target)) {
setShowSendMessageDropdown(false);
}
};

if (showSendMessageDropdown) {
document.addEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [showSendMessageDropdown]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Close the send dropdown when the connection ends.

If the connection drops while the dropdown is open, the listener stays attached and the menu can re‑open unexpectedly on reconnect. Reset the dropdown when isConnectionActive flips false.

🔧 Suggested fix
-  useEffect(() => {
+  useEffect(() => {
+    if (!isConnectionActive) {
+      setShowSendMessageDropdown(false);
+      return;
+    }
     const handleClickOutside = (event) => {
       if (sendMessageDropdownRef.current && !sendMessageDropdownRef.current.contains(event.target)) {
         setShowSendMessageDropdown(false);
       }
     };

     if (showSendMessageDropdown) {
       document.addEventListener('mousedown', handleClickOutside);
     }

     return () => {
       document.removeEventListener('mousedown', handleClickOutside);
     };
-  }, [showSendMessageDropdown]);
+  }, [showSendMessageDropdown, isConnectionActive]);
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js` around
lines 327 - 342, The dropdown-close effect currently only tracks
showSendMessageDropdown, so if isConnectionActive becomes false the menu can
remain open and its listener stays attached; update the effect (or add a small
effect) to also depend on isConnectionActive and when isConnectionActive is
false call setShowSendMessageDropdown(false) and ensure the existing cleanup
still removes the mousedown listener (keep sendMessageDropdownRef,
setShowSendMessageDropdown, showSendMessageDropdown and the handleClickOutside
logic intact).
Comment on lines +483 to +542
{/* Show connection controls for server-streaming when connected */}
{isConnectionActive && isServerStreamingMethod && (
<div className="connection-controls relative flex items-center h-full gap-3">
<div className="infotip" onClick={handleCancelConnection} data-testid="grpc-cancel-connection-button">
<IconX color={theme.colors.text.danger} strokeWidth={1.5} size={20} className="cursor-pointer" />
<span className="infotip-text text-xs">Cancel</span>
</div>
</div>
)}

{/* Show connection controls for client-streaming/bidi-streaming when connected (interactive mode) */}
{isConnectionActive && isClientStreamingMethod && (
<div className="connection-controls relative flex items-center h-full gap-3">
<div className="infotip" onClick={handleCancelConnection} data-testid="grpc-cancel-connection-button">
<IconX color={theme.requestTabs.icon.color} strokeWidth={1.5} size={20} className="cursor-pointer" />
<IconX color={theme.colors.text.danger} strokeWidth={1.5} size={20} className="cursor-pointer" />
<span className="infotip-text text-xs">Cancel</span>
</div>

{isClientStreamingMethod && (
<div onClick={handleEndConnection} data-testid="grpc-end-connection-button">
<IconCheck
color={theme.colors.text.green}
strokeWidth={2}
size={20}
className="cursor-pointer"
/>
<div className="infotip" onClick={handleEndConnection} data-testid="grpc-end-connection-button">
<IconCheck color={theme.colors.text.green} strokeWidth={2} size={20} className="cursor-pointer" />
<span className="infotip-text text-xs">End Stream</span>
</div>

{/* Send message dropdown */}
<div className="send-dropdown-container" ref={sendMessageDropdownRef}>
<div
className="send-dropdown-trigger cursor-pointer flex items-center"
onClick={(e) => {
e.stopPropagation();
setShowSendMessageDropdown(!showSendMessageDropdown);
}}
data-testid="grpc-send-message-dropdown-trigger"
>
<IconSend color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={18} />
<IconChevronDown color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={14} />
</div>
)}

{showSendMessageDropdown && (
<div className="send-dropdown-menu">
<div
className="send-dropdown-item"
onClick={handleSendFirstMessage}
data-testid="grpc-send-first-message"
>
<IconSend size={14} strokeWidth={1.5} />
<span>Send Message</span>
</div>
<div
className={`send-dropdown-item ${enabledMessages.length === 0 ? 'disabled' : ''}`}
onClick={enabledMessages.length > 0 ? handleSendAllMessages : undefined}
data-testid="grpc-send-all-messages"
>
<IconSend size={14} strokeWidth={1.5} />
<span>Send All ({enabledMessages.length})</span>
</div>
</div>
)}
</div>
</div>
)}
Copy link
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:

cat -n packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js | sed -n '483,570p'

Repository: usebruno/bruno

Length of output: 4989


🏁 Script executed:

# Check file size to understand if we can read more context
wc -l packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js

Repository: usebruno/bruno

Length of output: 129


🏁 Script executed:

# Look for any existing keyboard event handlers in the file
rg 'onKeyDown|onKeyUp|onKeyPress' packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js -A 2 -B 2

Repository: usebruno/bruno

Length of output: 40


🏁 Script executed:

# Check if other components in RequestPane use buttons vs divs for controls
rg 'onClick=' packages/bruno-app/src/components/RequestPane/ -t jsx -A 1 | head -50

Repository: usebruno/bruno

Length of output: 85


🏁 Script executed:

# Look for other interactive divs without button role in the codebase
fd '\.jsx?$' packages/bruno-app/src/components/RequestPane/ | xargs rg 'onClick=.*?className="[^"]*cursor-pointer' -l

Repository: usebruno/bruno

Length of output: 42


🏁 Script executed:

# Check if there are any accessibility-related utilities or patterns established
rg 'role="button"|tabIndex|onKeyDown.*button' packages/bruno-app/src/components/ -l | head -10

Repository: usebruno/bruno

Length of output: 781


Make the connection/send controls keyboard-accessible.

The clickable <div> elements at lines 486, 496, 501, 508–518, 522–529, 530–537, 547–554, and 555–565 aren't keyboard-focusable, blocking keyboard and assistive-tech users. Convert these to <button type="button"> or add role="button", tabIndex="0", and onKeyDown handlers. Also apply disabled attribute to the "Send All" button when enabledMessages.length === 0 (line 531) instead of relying on conditional className.

Example conversion pattern
-            <div
+            <button
+              type="button"
               className="send-dropdown-trigger cursor-pointer flex items-center"
               onClick={(e) => {
                 e.stopPropagation();
                 setShowSendMessageDropdown(!showSendMessageDropdown);
               }}
               data-testid="grpc-send-message-dropdown-trigger"
             >
               <IconSend color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={18} />
               <IconChevronDown color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={14} />
-            </div>
+            </button>
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/components/RequestPane/GrpcQueryUrl/index.js` around
lines 483 - 542, The connection/send controls use non-focusable <div>s; update
the interactive elements tied to handleCancelConnection, handleEndConnection,
the send-dropdown trigger (sendMessageDropdownRef / showSendMessageDropdown),
handleSendFirstMessage, and handleSendAllMessages so they are
keyboard-accessible: replace those clickable <div> wrappers with <button
type="button"> elements (preferred) or add role="button", tabIndex={0}, and
onKeyDown handlers that call the same handlers on Enter/Space; also ensure the
"Send All" control uses the disabled attribute when enabledMessages.length === 0
(instead of only a disabled class) and prevent its click/keyboard handlers when
disabled. Ensure aria-label or visible text remains for screen readers.
Comment on lines +572 to +589
const methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType;
const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming';

if (isClientStreaming) {
// Atomic connect-and-send for client-streaming and bidi-streaming
connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
});
} else {
// Existing behavior for unary and server-streaming
sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Resolve/reject the promise on connect-and-send failures.

Right now the catch blocks only toast, so sendRequest stays pending on errors in streaming mode. Recommend rejecting to avoid hung callers awaiting dispatch.

💡 Suggested fix
       if (isClientStreaming) {
         // Atomic connect-and-send for client-streaming and bidi-streaming
         connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
           .then(resolve)
           .catch((err) => {
             toast.error(err.message);
+            reject(err);
           });
       } else {
         // Existing behavior for unary and server-streaming
         sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
           .then(resolve)
           .catch((err) => {
             toast.error(err.message);
+            reject(err);
           });
       }
📝 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 methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType;
const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming';
if (isClientStreaming) {
// Atomic connect-and-send for client-streaming and bidi-streaming
connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
});
} else {
// Existing behavior for unary and server-streaming
sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
});
}
const methodType = itemCopy.draft?.request?.methodType || itemCopy.request?.methodType;
const isClientStreaming = methodType === 'client-streaming' || methodType === 'bidi-streaming';
if (isClientStreaming) {
// Atomic connect-and-send for client-streaming and bidi-streaming
connectAndSendGrpc(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
reject(err);
});
} else {
// Existing behavior for unary and server-streaming
sendGrpcRequest(itemCopy, collectionCopy, environment, collectionCopy.runtimeVariables)
.then(resolve)
.catch((err) => {
toast.error(err.message);
reject(err);
});
}
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js`
around lines 572 - 589, The promise path for client-streaming/bidi-streaming
uses connectAndSendGrpc(...) but its catch only toasts the error, leaving the
outer promise unresolved; update the catch to call the promise reject (same as
resolve path) so callers aren't left pending—i.e., in the catch handler for
connectAndSendGrpc(itemCopy, collectionCopy, environment,
collectionCopy.runtimeVariables) invoke reject(err) after/to accompany
toast.error(err.message); similarly ensure sendGrpcRequest(...) catch also
rejects if it should mirror behavior; locate these by the connectAndSendGrpc and
sendGrpcRequest calls inside the sendRequest/sendGrpc action and add reject(err)
in their .catch handlers.
Comment on lines +1646 to +1666
toggleGrpcMessageEnabled: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);

if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);

if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}

const messages = item.draft.request.body.grpc;
const messageIndex = action.payload.messageIndex;

if (messages && messages[messageIndex] !== undefined) {
const currentEnabled = messages[messageIndex].enabled !== false;
messages[messageIndex].enabled = !currentEnabled;
}
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard non‑gRPC items before reading body.grpc.

isItemARequest includes http/ws/graphql, so a stray dispatch could throw when body.grpc is missing. A quick type/array guard keeps this reducer safe.

💡 Suggested fix
-          const messages = item.draft.request.body.grpc;
+          if (item.type !== 'grpc-request') {
+            return;
+          }
+          const messages = item.draft.request.body?.grpc;
+          if (!Array.isArray(messages)) {
+            return;
+          }
📝 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
toggleGrpcMessageEnabled: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
const messages = item.draft.request.body.grpc;
const messageIndex = action.payload.messageIndex;
if (messages && messages[messageIndex] !== undefined) {
const currentEnabled = messages[messageIndex].enabled !== false;
messages[messageIndex].enabled = !currentEnabled;
}
}
}
},
toggleGrpcMessageEnabled: (state, action) => {
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
if (collection) {
const item = findItemInCollection(collection, action.payload.itemUid);
if (item && isItemARequest(item)) {
if (!item.draft) {
item.draft = cloneDeep(item);
}
if (item.type !== 'grpc-request') {
return;
}
const messages = item.draft.request.body?.grpc;
if (!Array.isArray(messages)) {
return;
}
const messageIndex = action.payload.messageIndex;
if (messages && messages[messageIndex] !== undefined) {
const currentEnabled = messages[messageIndex].enabled !== false;
messages[messageIndex].enabled = !currentEnabled;
}
}
}
},
🤖 Prompt for AI Agents
In `@packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js`
around lines 1646 - 1666, The reducer toggleGrpcMessageEnabled reads
item.draft.request.body.grpc without ensuring the request body contains a gRPC
messages array; update the guard inside toggleGrpcMessageEnabled (after
isItemARequest and after creating item.draft) to verify item.draft.request and
that item.draft.request.body.grpc is an array (e.g., const messages =
item.draft.request.body.grpc; proceed only if Array.isArray(messages) and
messages[messageIndex] !== undefined) before accessing or toggling
messages[messageIndex].enabled to prevent runtime errors for non‑gRPC requests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1 participant