From 4994a02db4c6834b0cc1f96ae4dff13e2c872fbf Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 28 Aug 2026 23:12:52 +0530 Subject: [PATCH 1/2] feat: list Hive on the site's apps screen Register the app with `add_to_apps_screen` so Hive appears on /apps and in other apps' app switchers, gated by `has_hive_access` so only System Managers, Hive Team and Hive Client users see the row. The logo lives in the app's own public folder rather than the Vite build output, which is gitignored. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014AupF7Eoc6JnGv1gaiQY6G --- bwh_hive/bwh_hive/permissions.py | 16 ++++++++++++++++ bwh_hive/hooks.py | 18 +++++++++--------- bwh_hive/public/images/hive-mark.svg | 1 + 3 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 bwh_hive/public/images/hive-mark.svg diff --git a/bwh_hive/bwh_hive/permissions.py b/bwh_hive/bwh_hive/permissions.py index e33b9a1..7b79155 100644 --- a/bwh_hive/bwh_hive/permissions.py +++ b/bwh_hive/bwh_hive/permissions.py @@ -200,3 +200,19 @@ def pinned_project_has_permission(doc, ptype: str | None = None, user: str | Non if ptype == "create": return True return doc.user == user + + +# Roles that grant access to Hive at all. Anyone on the team or on a client's +# side belongs in the app; everyone else is kept off the apps screen. +HIVE_ROLES = ("System Manager", "Hive Team", "Hive Client") + + +def has_hive_access(user: str | None = None) -> bool: + """Whether `user` is allowed to see Hive at all.""" + if not user: + user = frappe.session.user + + if user == "Administrator": + return True + + return any(role in frappe.get_roles(user) for role in HIVE_ROLES) diff --git a/bwh_hive/hooks.py b/bwh_hive/hooks.py index ed57b99..cf4ade6 100644 --- a/bwh_hive/hooks.py +++ b/bwh_hive/hooks.py @@ -11,15 +11,15 @@ # required_apps = [] # Each item in the list will be shown as an app in the apps page -# add_to_apps_screen = [ -# { -# "name": "bwh_hive", -# "logo": "/assets/bwh_hive/logo.png", -# "title": "BWH Hive", -# "route": "/bwh_hive", -# "has_permission": "bwh_hive.api.permission.has_app_permission" -# } -# ] +add_to_apps_screen = [ + { + "name": "bwh_hive", + "logo": "/assets/bwh_hive/images/hive-mark.svg", + "title": "Hive", + "route": "/hive", + "has_permission": "bwh_hive.bwh_hive.permissions.has_hive_access", + } +] # Includes in # ------------------ diff --git a/bwh_hive/public/images/hive-mark.svg b/bwh_hive/public/images/hive-mark.svg new file mode 100644 index 0000000..31f0905 --- /dev/null +++ b/bwh_hive/public/images/hive-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file From 58f03308de7cf9049c1a3df84a3fe6f84dac423a Mon Sep 17 00:00:00 2001 From: Hussain Nagaria Date: Fri, 28 Aug 2026 23:20:02 +0530 Subject: [PATCH 2/2] feat: add an app switcher to the sidebar header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header dropdown gains a "Switch app" submenu built from `frappe.apps.get_apps`, so it tracks whatever the site has installed without a Hive-side registry. Hive itself is filtered out. Desk is not on the apps screen, so it is added by hand — only for System Managers, since a client user would land on a permission error. The flag rides along in the boot dict rather than costing a request. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014AupF7Eoc6JnGv1gaiQY6G --- bwh_hive/www/hive.py | 2 + e2e/tests/app-switcher.spec.ts | 35 +++++++++++ frontend/src/components/shell/AppSidebar.vue | 61 ++++++++++++++++++-- frontend/src/composables/useSession.ts | 4 ++ frontend/src/env.d.ts | 1 + 5 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 e2e/tests/app-switcher.spec.ts diff --git a/bwh_hive/www/hive.py b/bwh_hive/www/hive.py index cb01b38..9ed73f7 100644 --- a/bwh_hive/www/hive.py +++ b/bwh_hive/www/hive.py @@ -26,5 +26,7 @@ def get_boot(): "site_name": frappe.local.site, "read_only_mode": frappe.flags.read_only, "system_timezone": get_system_timezone(), + # The app switcher offers Desk only to those who can actually open it. + "is_system_manager": "System Manager" in frappe.get_roles(), } ) diff --git a/e2e/tests/app-switcher.spec.ts b/e2e/tests/app-switcher.spec.ts new file mode 100644 index 0000000..7fbf2d2 --- /dev/null +++ b/e2e/tests/app-switcher.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from "../helpers/app"; +import { callMethodGet } from "../helpers/frappe"; +import { gotoHive } from "../helpers/ui"; + +/** + * The switcher is fed by `frappe.apps.get_apps` — the site-wide apps-screen + * list — so what it offers depends on which apps the site has installed. The + * one row it always has is Desk, added client-side for System Managers. + */ +test.describe("App switcher", () => { + test("lists Desk and the site's other apps, but not Hive itself", async ({ + page, + request, + }) => { + const apps = await callMethodGet< + { name: string; title: string; route: string }[] + >(request, "frappe.apps.get_apps"); + + await gotoHive(page, "/"); + + await page.locator('[data-slot="sidebar-header"] button').click(); + await page.getByRole("menuitem", { name: "Switch app" }).hover(); + + const desk = page.getByRole("menuitem", { name: "Desk" }); + await expect(desk).toBeVisible({ timeout: 10000 }); + await expect(page.getByRole("menuitem", { name: "Hive" })).toHaveCount(0); + + for (const app of apps.filter((app) => app.name !== "bwh_hive")) { + await expect(page.getByRole("menuitem", { name: app.title })).toBeVisible(); + } + + await desk.click(); + await expect(page).toHaveURL(/\/desk/); + }); +}); diff --git a/frontend/src/components/shell/AppSidebar.vue b/frontend/src/components/shell/AppSidebar.vue index 37d4747..83f90db 100644 --- a/frontend/src/components/shell/AppSidebar.vue +++ b/frontend/src/components/shell/AppSidebar.vue @@ -58,7 +58,8 @@