Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
packages/react-grab/src/components/toolbar/toolbar-entry-container.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
5 issues found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/core/index.tsx">
<violation number="1" location="packages/react-grab/src/core/index.tsx:3898">
P2: `dismissAllPopups` correctly includes `dismissToolbarEntry()`, and `handleToggleToolbarEntry` dismisses other popups before opening — but the reverse is not true. The existing popup-opening functions (`handleToggleToolbarMenu`, `openCommentsDropdown`, `showClearPrompt`) were not updated to call `dismissToolbarEntry()`. Opening any of those while a toolbar entry dropdown is visible will leave both popups visible simultaneously.</violation>
<violation number="2" location="packages/react-grab/src/core/index.tsx:3903">
P2: Dismiss the active toolbar entry before opening a new one to ensure its `onClose` fires. Also consider adding `dismissToolbarEntry()` to the other existing popup handlers to maintain mutual exclusion.</violation>
</file>
<file name="packages/react-grab/src/components/toolbar/toolbar-content.tsx">
<violation number="1" location="packages/react-grab/src/components/toolbar/toolbar-content.tsx:296">
P2: The nullish coalescing operator (`??`) prevents clearing badges. A plugin calling `handle.setBadge(undefined)` cannot hide an initial badge because `undefined ?? fallback` returns the fallback.</violation>
</file>
<file name="packages/gym/components/toolbar-entries-provider.tsx">
<violation number="1" location="packages/gym/components/toolbar-entries-provider.tsx:99">
P1: The click handler does not actually toggle the status, contradicting the test plan.</violation>
</file>
<file name="packages/react-grab/src/core/plugin-registry.ts">
<violation number="1" location="packages/react-grab/src/core/plugin-registry.ts:199">
P2: SolidJS `setStore` deep-merges objects and does not delete missing keys. Set the removed keys to `undefined` to actually delete them from the store.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 18 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/react-grab/src/core/index.tsx">
<violation number="1" location="packages/react-grab/src/core/index.tsx:3858">
P2: Stale `toolbarEntryOverrides` are never cleaned up on plugin unregister. The old cleanup block in `plugin-registry.ts` was removed, and the new `unregisterPlugin` code here only dismisses the active dropdown. If a plugin is unregistered and later re-registered with the same entry IDs, overrides from the previous lifecycle (set via `handle.setIcon`, `handle.setBadge`, etc.) silently persist and override the fresh entry's properties. Add cleanup of `toolbarEntryOverrides` for the removed plugin's entry IDs, either here or back in the registry's `unregister` method.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
2 issues found across 14 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/gym/components/toolbar-entries-provider.tsx">
<violation number="1" location="packages/gym/components/toolbar-entries-provider.tsx:46">
P2: The `traverseFiber` function increments the render count for every component in the fiber tree unconditionally on every commit. This effectively counts 'components × commits' rather than identifying which components actually rendered. To accurately measure renders, verify if the fiber updated during the commit (e.g., by checking `fiber.memoizedProps !== fiber.alternate?.memoizedProps` or using `fiber.flags`).</violation>
<violation number="2" location="packages/gym/components/toolbar-entries-provider.tsx:86">
P1: When intercepting `onCommitFiberRoot`, the wrapper function explicitly forwards only `rendererID` and `fiberRoot`. If the original `onCommitFiberRoot` expects additional arguments (like `commitPriority` or `didError` in newer React versions), they will be dropped, which can break React DevTools. Use rest parameters to forward all arguments.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/gym/components/toolbar-entries-provider.tsx">
<violation number="1" location="packages/gym/components/toolbar-entries-provider.tsx:65">
P2: This props/state equality check misses real React renders, so the render monitor undercounts parent- and context-driven updates.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| pluginRegistry.updateToolbarEntry(entryId, { badge }), | ||
| setVisible: (isVisible) => | ||
| pluginRegistry.updateToolbarEntry(entryId, { isVisible }), | ||
| }); |
There was a problem hiding this comment.
New handle created each call defeats documented caching
Low Severity
getToolbarEntryHandle creates a brand-new object on every invocation, contradicting the PR description's claim of "cache handles per entry ID for stable identity." While currently functional (methods operate by entryId), the lack of a Map-based cache means every call to activeToolbarEntryHandle() or handleToggleToolbarEntry allocates a fresh object. If any consumer later relies on referential identity (e.g., in a reactive comparison or as a Map key), it will silently break.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/gym/components/toolbar-entries-provider.tsx">
<violation number="1" location="packages/gym/components/toolbar-entries-provider.tsx:57">
P2: `fiber.flags > 0` is not a reliable re-render signal; static fiber flags will make the render monitor overcount memoized components.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| fiber.memoizedProps !== fiber.alternate.memoizedProps || | ||
| fiber.memoizedState !== fiber.alternate.memoizedState | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Render monitor flags check overcounts fiber renders
Medium Severity
The didFiberRender function's fiber.flags && fiber.flags > 0 check is far too broad. React fiber flags is a bitfield where many bits (like Ref, Placement, ChildDeletion, ContentReset) can be set on fibers that didn't actually re-render their component function. This causes the render monitor to significantly overcount renders, directly contradicting the PR's stated fix of "count only fibers that actually re-rendered." A more targeted check like fiber.flags & 1 (the PerformedWork bit) would match only fibers whose component function was actually called.
| handle.setBadge(totalRenderCount); | ||
| } | ||
| }; | ||
| }; |
There was a problem hiding this comment.
Monitoring state inconsistent when devtools hook missing
Low Severity
startMonitoring sets isMonitoring = true on line 87 before checking if __REACT_DEVTOOLS_GLOBAL_HOOK__ exists on line 96. When the hook is absent, the function returns early with isMonitoring stuck as true but no actual monitoring installed. The toolbar button icon turns red (active appearance) even though nothing is being tracked, and the badge never updates — misleading the user into thinking monitoring is working.
Allow plugins to register persistent buttons in the toolbar strip via `toolbarEntries`. Entries support emoji/SVG/HTML icons, optional dropdown containers (onRender), action-only buttons (onClick), badges, visibility control, and lifecycle hooks (onOpen/onClose). Handles are cached per entry and provide full ReactGrabAPI access plus dropdown and display control. Includes gym demo with debug panel, screenshot action, and status indicator entries registered globally.
Remove redundant onOpen/onClose lifecycle hooks, eliminate toolbarEntryOverrides prop threading through 3 component layers, and drop the handle cache + stale cleanup bookkeeping.
Pre-merging overrides via object spread broke <For> referential identity and caused the container effect to re-fire onRender on every badge/icon change, resetting closure state.
Render monitor hooks into __REACT_DEVTOOLS_GLOBAL_HOOK__ to count component renders with per-component breakdown. FPS meter tracks frame timing via rAF with sparkline graph and drop detection. Test page has intentionally laggy components to exercise both.
Move toolbar entries to top of plugins section, consolidate actions and hooks examples, move manual installation below plugins. Remove all em-dashes from repo.
- Add dismissToolbarEntry() to all popup openers for mutual exclusion - Fix badge clearing: use "in" check instead of ?? so setBadge(undefined) properly clears badges instead of falling through to the original value - Restore toolbarEntryOverrides cleanup on plugin unregister to prevent stale overrides persisting across re-registrations
SolidJS setStore deep-merges objects, so removed keys persist. Using reconcile ensures stale overrides are actually deleted when a plugin is unregistered. Also includes previously uncommitted README updates and script.js changes.
- Use rest params for onCommitFiberRoot to forward all arguments - Only count fibers that actually rendered (check alternate props/state) - Remove dead patchedRoots WeakSet code - Add dispose() to stop rAF loop and unhook devtools on plugin unregister
The props/state reference equality check alone misses renders triggered by context changes or parent re-renders where React sets flags on the fiber without changing memoizedProps/memoizedState references.
b34b49a to
191dedc
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| setActiveToolbarEntryId(entryId); | ||
| openTrackedDropdown(setToolbarEntryDropdownPosition); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Calling handle.open() from onClick causes infinite recursion
Medium Severity
If a plugin's onClick callback calls handle.open() or handle.toggle(), it re-enters handleToggleToolbarEntry before setActiveToolbarEntryId has been called. Because the active ID hasn't been set yet, the guard in handle.open() (activeToolbarEntryId() !== entryId) passes again, triggering another handleToggleToolbarEntry → onClick → handle.open() cycle, resulting in a stack overflow. The same applies to action-only entries (no onRender), where onClick is called unconditionally.


Summary
toolbarEntriesto the plugin system, allowing plugins to register persistent buttons in the toolbar strip with emoji, SVG, or HTML iconsonClickwithout dropdown) and dropdown-based (onRenderwith a framework-agnostic container)ToolbarEntryHandleper entry for dropdown control (open/close/toggle), display updates (setIcon/setBadge/setTooltip/setVisible), and fullReactGrabAPIaccessAGENTS.md compliance fixes applied
createEffectevent-bus pattern with direct cleanup inregisterPlugin/unregisterPlugincall sitesmountCleanupvariable tracking with idiomaticonCleanup()inside the effectactiveToolbarEntryHandle)entryloop/callback variables totoolbarEntryfor descriptive namingTest plan
pnpm build— no type errorspnpm typecheck— passespnpm lint— 0 warnings, 0 errors/dashboardshows 3 toolbar entry buttons/toolbar-entriesshows documentation pagehandle.setBadge(n)shows badge on buttonNote
Medium Risk
Adds a new plugin-facing toolbar entry API and new dropdown rendering path in the overlay UI, which could impact toolbar interactions and popup dismissal behavior. Risk is mitigated by localized changes and updated tests/docs, but it introduces new state and DOM rendering hooks.
Overview
Adds a plugin toolbar entries API to
react-grab, letting plugins register persistent toolbar buttons with optional dropdown panels viatoolbarEntries, plus aToolbarEntryHandleto control open/close state and dynamically update icon/tooltip/badge/visibility.Updates the renderer/core to manage a mutually-exclusive toolbar-entry dropdown (new
ToolbarEntryContainer), wire entry clicks through the toolbar UI, track active entry state, and clean up per-entry overrides when plugins unregister; also exposestoggleToolbarEntry/closeToolbarEntryon the public API.Includes a gym demo and performance test page (
/toolbar-entries) that registers FPS/Render monitor entries, and refreshes plugin documentation in root/packaged READMEs; remaining changes are mostly formatting/cleanup in CLI/tests/docs.Written by Cursor Bugbot for commit 191dedc. This will update automatically on new commits. Configure here.
Summary by cubic
Adds a plugin-driven toolbar entries API to
react-grabso plugins can add persistent toolbar buttons with optional dropdown UIs, live icon/badge updates, and programmatic control. Ships a Render Monitor and FPS Monitor in the gym with a new/toolbar-entriespage, and updates the Plugins docs.New Features
toolbarEntriesfor plugin buttons (emoji/SVG/HTML icons, badges, visibility) with action (onClick) or dropdown (onRender) modes and a per-entryToolbarEntryHandle.toggleToolbarEntry(entryId),closeToolbarEntry(). ExportToolbarEntryandToolbarEntryHandletypes.__REACT_DEVTOOLS_GLOBAL_HOOK__) and FPS Monitor; new/toolbar-entriesperformance page.Bug Fixes
setBadge(undefined)clears badges. RestorestoolbarEntryOverridesreactivity and cleans overrides on plugin unregister using Solidreconcile.Written for commit 191dedc. Summary will update on new commits.