feat: add plugin navigation and custom permissions support#275
Conversation
- Introduced new sidebar navigation items for plugins in both project and admin scopes. - Enhanced plugin manifest to include custom permissions, allowing plugins to define their own permission keys. - Implemented backend support for checking custom permissions via the `permission_check` function. - Added routes for rendering plugin components in both project and admin contexts. - Updated internationalization files to include new terms related to plugins in multiple languages. - Refactored permission normalization logic to accommodate plugin-defined permissions. - Improved query parameter handling in plugin requests to ensure they are forwarded correctly.
|
See the formal review posted above for detailed feedback.
|
There was a problem hiding this comment.
test body
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Important
This PR introduces plugin navigation and custom permissions, but several gaps need addressing before merge: global-scoped custom permissions are not wired into the global role editor, the project role editor can miss plugin permissions on first open, the new admin and project plugin routes lack appropriate permission gates, and plugin-declared permission keys are not validated for collisions with built-in permissions.
Reviewed changes — plugin-contributed sidebar navigation for project and admin scopes, plugin-declared custom permissions surfaced in the project role editor, backend permission_check host function in the plugin runtime, and query-param forwarding for plugin HTTP routes.
- Added
project.pageandadmin.pageextension points with TanStack Router routes and sidebar nav-item helpers inapp-shell/app-sidebar.tsx. - Extended plugin manifests with
navItemsandcustomPermissions; wired project-scoped custom permissions intoProjectRoleFormDialogviacollectPluginCustomPermissions/toPluginKnownPermissions. - Added backend
paca.permission_checkhost function ininternal/platform/plugin/runtime.gousing the sharedAuthorizer. - Forwarded URL query parameters through
PluginHandler.ProxyRequestand added an e2e regression test. - Updated i18n files for the new sidebar group and extension point labels across all supported locales.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes — the new commits address all prior Pullfrog feedback:
- Wired global-scoped custom permissions into the global role editor (
RoleFormDialog) and added a syntheticpluginspermission group, mirroring the project role editor. - Re-derived permission state in both role editors via
useEffectwhen the dialog opens orallKnownPermissionschanges, so plugin permissions loaded after mount are no longer silently dropped. - Filtered sidebar plugin pages by each nav item's
requiredPermissionand made the admin section render only when at least one visible plugin admin item exists. - Added route-level permission gates to the admin and project plugin pages, reading each nav item's
requiredPermission(admin falls back tousers.write). - Added
PluginManifest.Validate()to enforce that custom permission keys are namespaced under the plugin's derived ID prefix.
ℹ️ Test coverage gaps
The only remaining carry-forward risk is missing automated coverage for the new behavior these incremental commits introduced: PluginManifest.Validate() namespace enforcement, route-level permission gates, and the existing paca.permission_check host function. Worth adding tests or a follow-up issue.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
…ialog for stable reference

Summary
Lets plugins contribute full, routed sidebar pages (not just embedded extension-point fragments) and declare their own permission keys that plug into the existing role editor and permission checks.
Changes
Plugin-owned pages (
project.page/admin.pageextension points)frontend.navItems[]in its manifest, each routing to a full-page component instead of an embedded<ExtensionPoint>fragment./projects/:projectId/plugins/:pluginId/:slugand/admin/plugins/:pluginId/:slugrender the matched component viaRemoteComponent, with a load-failure fallback.PluginProjectPages/PluginAdminPagesinapp-sidebar.tsx, sourced fromusePluginRegistry().getNavItems().resolvePluginIconmaps a manifest's lucide-react icon name to a component, falling back to a generic puzzle icon for typos/omissions.Plugin-declared custom permissions
customPermissions[](key/label/description/scope) so plugins can define permission keys beyond the built-in set (e.g.time_logging.manage_all).paca.permission_checklets plugin backend code check a permission against the caller's effective project/global role — same resolution path as therequirePermissionsroute middleware — without a second host round-trip.normalizePermissionsToWildcardsto collapse permissions by their own key prefix instead of by UIdomain. Multiple plugins share the synthetic "plugins" domain, so domain-based collapsing would've produced a bogusplugins.*key and over-granted every other plugin's permissions in that group.Bug fix: plugin request query params were dropped
PluginHandler.ProxyRequestnever readr.URL.Query(), soreq.QueryParam(...)inside every plugin backend always saw an empty map — query-string-driven filtering/pagination was silently a no-op for all plugins. Now forwarded throughHTTPRequest.Query.TestE2EPluginRuntime_APICall_QueryParamsPropagate.Other
docker-compose.dev.yml: stop publishing Postgres's port 5432 to the host.plugins/projectPage/adminPagestrings across all locales.Testing
TestE2EPluginRuntime_APICall_QueryParamsPropagate(new e2e test covering the query-param fix)