-
Notifications
You must be signed in to change notification settings - Fork 241
chore: refactor and centralize app menu access COMPASS-9976 #7493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Instead of having the logic for accessing the Electron application menu from the collection tabs spread out through the codebase, bundle it in a single interface that gives all Compass plugins access to the menu as needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors how Compass plugins access the Electron application menu by introducing a centralized ApplicationMenuProvider interface. Instead of having collection-specific menu logic spread across multiple files, it consolidates menu management into a single, reusable pattern.
- Introduces
ApplicationMenuProviderinterface anduseApplicationMenuhook for managing application menus from any component - Replaces direct IPC calls for collection menu visibility with a more flexible menu registration system
- Moves collection menu logic from main process into the collection tab component itself
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/compass/src/main/menu.ts | Refactored to support dynamic menu registration via additionalMenus and roleListeners instead of hardcoded collection submenu |
| packages/compass/src/main/menu.spec.ts | Updated tests to reflect new menu state structure and removed tests for deprecated collection submenu methods |
| packages/compass/src/app/components/home.tsx | Removed collection submenu callbacks, added ApplicationMenuContextProvider |
| packages/compass/src/app/application.tsx | Implemented ApplicationMenu class providing menu provider interface, removed old IPC listeners for schema/import/export |
| packages/compass-workspaces/src/index.ts | Removed global app registry listeners for menu actions (now handled locally) |
| packages/compass-workspaces/src/application-menu.tsx | New file defining menu provider interface, context, and useApplicationMenu hook |
| packages/compass-workspaces/package.json | Added new application-menu export entry point |
| packages/compass-collection/src/components/collection-tab.tsx | Integrated useApplicationMenu hook to register collection menu when component is mounted |
Comments suppressed due to low confidence (2)
packages/compass-workspaces/src/application-menu.tsx:1
- The dependency array performs
JSON.stringifyandtransformAppMenuon every render, which could be expensive for large menus. Consider memoizing these computed values usinguseMemoto avoid recalculating them unlessmenuorrolesactually change.
import React, { useContext, useEffect } from 'react';
packages/compass-collection/src/components/collection-tab.tsx:1
- [nitpick] The empty object passed as the fourth argument to
globalAppRegistry.emitappears unnecessary. If this parameter is unused, consider removing it or adding a comment explaining its purpose for clarity.
import React, { useCallback, useEffect } from 'react';
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also wanted to call out this entry point in particular, if there's a better place for this or we maybe should even put it into a separate package that would seem fine to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, left a comment about it before seeing yours: yeah, I'd probably suggest to move this to its own place, the logic is pretty contained and doesn't need to be tied to our compass plugins
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Very minor notes, but nothing blocking. Finally I can close #5818
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can probably be just completely in its own package
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gribnoysup Done – obviously that's a fairly large change to this PR, so please feel free to re-review 🙂
| return menu; | ||
| return [ | ||
| darwinCompassSubMenu(menuState, app), | ||
| connectSubMenu(false, app), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to move all feature specific menus to their locations in the app, but that's already a good start
Oh damn, I wasn't aware of that one! Will address your feedback today |
|
Haha, yeah, your implementation is solving a similar problem in a pretty similar way so I think it's a great replacement for this unfinished experiment I did! I tried to bake a bit more features in, but I don't think it's really required at the end of the day, definitely feel free to use it for inspiration if you feel like it though 😄 |
scripts/check-peer-deps.js
Outdated
| const shouldSkip = (path) => | ||
| path.node.leadingComments?.some((comment) => | ||
| comment.value?.includes('compass-peer-deps-ignore') | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually wasn't aware that we're doing this via a custom script, cool! Looks like ImportDeclaration node has a prop importKind?: "type" | "typeof" | "value" | null;. Maybe we could ignore the type imports automatically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gribnoysup I assume we intentionally included type dependencies here ... if that isn't the case, then yeah, excluding them entirely makes sense to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, the main purpose here is to create a clear picture of what packages actually depend on via a set of straightforward rules to follow. Type imports should be part of the direct dependencies list, if they are not, you are potentially breaking consumers of your package
I see that the exception is made for electron, I guess this will go away if we move the application menu code to its own package. Can we remove the ability to make exceptions for the rules if we will not be using them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type imports should be part of the direct dependencies list, if they are not, you are potentially breaking consumers of your package
True. That's an implicit requirement that this package adds to its dependents.
I see that the exception is made for
electron, I guess this will go away if we move the application menu code to its own package. Can we remove the ability to make exceptions for the rules if we will not be using them?
The same logic applies even now that it's in a separate package, unfortunately :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm maybe missing something, but I'd like to clarify this. So this is a real type dependency for this package, this is a type-only dependency, so even though it will end up in a transitive "production" dependency tree of packages that use it and are not bundled, it will not end up in the actual compass-web bundle or its production dependencies (only in dev ones as this is build time), so what's preventing us from adding this as a prod dependency in this package if this should be a prod dependency? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gribnoysup I've undone that in 07da41f.
I don't think this is the right choice in general for npm packages – yes, it being a type-only dependency means that it's something that will require the electron package to be present in dependent packages. But that requirement only exists at build time, so for "normal" npm packages I would never add it as a production dependency – there would just be an implicit requirement to have electron available as a devDependency in consuming packages as well. (If npm provided something like "peer dev dependency" packages, that would be the right category.)
You're right that we can ignore that concern because we're targeting only Compass here. Personally, I still like to follow what I'd consider a best practice for our packages even if our target context doesn't really require that best practice to be followed. But this is definitely not a hill I am prepared to die on in this case 🙂
| import { uuid } from './util'; | ||
| import createDebug from 'debug'; | ||
| const debug = createDebug('compass-electron-menu:ipc-provider-renderer'); | ||
| export interface HadronIpcRenderer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious why are you not just adding a dependency on hadron-ipc here, types and all (or at least for types), the package is safe to import in browser environment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two reasons, although they're not exactly huge ones:
- We get type safety for the events by using the event map type here for the events/calls defined in this package
- It's easier to provide a typesafe mock interface in the test if we just restrict ourselves to what we use
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, gotcha, makes sense! Makes me think that maybe it's worth extending the HadronIpcRenderer / HadronIpcMain class to take type-safe event map optionally, but doesn't need to be something for this PR 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still looks good, but I'd really wouldn't want us to introduce any exceptions to the peer dep check rule, this was causing so much confusion in the past, was so misaligned across all packages, I really would want us to stick to the defined rules here, especially as it doesn't look like there's a reason to make an exception
| )} | ||
| showCollectionSubMenu={this.showCollectionSubMenu.bind(this)} | ||
| hideCollectionSubMenu={this.hideCollectionSubMenu.bind(this)} | ||
| applicationMenuProvider={this.menuProvider} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can probably keep the whole provider out of the entrypoint instead of passing the instance as a prop here and then passing it to provider component (but not that important, we need to do a big clean-up of entrypoints, so it can stay as is)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gribnoysup So, moving the provider component into the root render tree here? No strong feelings, but I do want to check that I understand your suggestion correctly 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep! Wrapping the whole here in your provider and passing it the ipc
(Split out from #7494 since I think this part is a bit clearer in that I think we want to do it for sure, and it's meaningful to review this separately)
Instead of having the logic for accessing the Electron application menu from the collection tabs spread out through the codebase, bundle it in a single interface that gives all Compass plugins access to the menu as needed.
Description
Checklist
Motivation and Context
Open Questions
Dependents
Types of changes