From 9885fd40baa4396c5098e3e81cd257bbe733ab0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Salavisa?= Date: Wed, 23 Sep 2026 21:30:36 +0100 Subject: [PATCH] fix(ui): polish mobile navigation and plugins (#3890) Co-authored-by: Kandev Agent --- apps/web/AGENTS.md | 3 +- .../app-sidebar-workspace-actions.tsx | 21 +- .../app-sidebar/shortcut-section.tsx | 6 + .../integrations/integrations-menu.test.ts | 17 ++ .../integrations/integrations-menu.tsx | 86 ++++-- .../main-top-bar-plugin-actions.test.tsx | 4 +- .../kanban/main-top-bar-plugin-actions.tsx | 2 +- .../kanban/mobile-listing-menu-actions.tsx | 2 +- .../navigation/app-nav-sections.tsx | 21 +- .../navigation/app-nav-sheet.test.tsx | 39 ++- .../components/navigation/app-nav-sheet.tsx | 46 +-- .../navigation/destination-rows.tsx | 44 ++- .../navigation/mobile-automations-section.tsx | 17 +- .../mobile-canvases-section.test.tsx | 33 +++ .../navigation/mobile-canvases-section.tsx | 88 ++++++ .../mobile-sidebar-layout-navigation.tsx | 280 ++++++++++++------ .../mobile-plugin-nav-section.test.tsx | 75 ++++- .../plugins/mobile-plugin-nav-section.tsx | 82 ++++- .../layout/mobile-menu-hierarchy.spec.ts | 2 +- .../plugins/mobile-plugin-topbar.spec.ts | 104 +++++-- .../mobile-sidebar-customization.spec.ts | 179 ++++++++++- .../tests/task/mobile-autopilot-mode.spec.ts | 10 +- .../mobile-plugin-menu-coherence/plan.md | 125 ++++++++ .../task-01-group-phone-plugins.md | 147 +++++++++ docs/plans/mobile-saved-navigation/plan.md | 109 +++++++ .../task-01-restore-phone-navigation.md | 127 ++++++++ .../task-02-consolidate-plugin-navigation.md | 101 +++++++ docs/public/plugins-authoring.md | 4 +- docs/public/sessions-and-review.md | 2 + docs/public/use-kandev.md | 5 +- .../ui/requirements/sidebar-customization.md | 8 +- .../requirements/unified-mobile-navigation.md | 8 +- .../ui/system-design/sidebar-customization.md | 20 +- .../unified-mobile-navigation.md | 38 ++- 34 files changed, 1637 insertions(+), 218 deletions(-) create mode 100644 apps/web/components/navigation/mobile-canvases-section.test.tsx create mode 100644 apps/web/components/navigation/mobile-canvases-section.tsx create mode 100644 docs/plans/mobile-plugin-menu-coherence/plan.md create mode 100644 docs/plans/mobile-plugin-menu-coherence/task-01-group-phone-plugins.md create mode 100644 docs/plans/mobile-saved-navigation/plan.md create mode 100644 docs/plans/mobile-saved-navigation/task-01-restore-phone-navigation.md create mode 100644 docs/plans/mobile-saved-navigation/task-02-consolidate-plugin-navigation.md diff --git a/apps/web/AGENTS.md b/apps/web/AGENTS.md index f35a4e7c2a..accf8a15b0 100644 --- a/apps/web/AGENTS.md +++ b/apps/web/AGENTS.md @@ -31,7 +31,8 @@ import { Dialog } from "@kandev/ui/dialog"; - Use `useTouchDrawer` when a hover/popover disclosure needs a coarse-pointer `Drawer` alternative. Width-based phone composition and pointer-based disclosure behavior are related but not interchangeable. Apply the 44px minimum to coarse-pointer hit areas, touch rows, and mobile controls only; keep fine-pointer desktop controls at the surrounding design-system density, using 28px for ordinary buttons, inputs, and selectors and 24px only for deliberate compact inline controls, and do not reuse a touch-sized `h-11` class as the shared visual button size. See the [sizing guide](../../.agents/skills/mobile-parity/references/control-sizing.md) for exceptions. - Below 640px, shared Radix DropdownMenu/ContextMenu use inset, safe-area-aware bottom sheets in `app/globals.css`. Open `mobile-menu-root` content owns one decorative positioner backdrop matching Drawer dimming and blur; fade it with the sheet's exit motion. Never mark submenus; keep the backdrop outside scrolling content and pointer-transparent so Radix owns dismissal and non-modal interaction. Reuse these primitives and cover long/nested menus instead of adding parallel mobile menus. - Mobile capability parity does not require desktop layout parity. Load `/mobile-parity` for the Kandev surface decision guide, mobile design contract, and verification requirements; read [confirmation guidance](components/confirmation/AGENTS.md) when adopting phone confirmation surfaces. -- Phone hamburger buttons use `AppNavSheet` for app navigation across listings, task workbenches, and page shells. The task title opens the task picker; `ResponsiveTaskPicker` owns its dialogs above responsive layout branches, and `TaskSheetSelectionProvider` shares cancellation with the embedded task sidebar. `MobileTaskNavigationProvider` retains the inline sidebar controller and action dialogs above responsive page headers, rendering the body into the shared menu outlet. Tasks collapses in place and shares the menu scroller. `KanbanHeaderMobile` opens listing-only `MobileMenuSheet` options from the Kanban/Threads/List title dropdown; Threads saved views render inline inside that surface. `MobileListingMenuActions` supplies search in options and workspace tools in app navigation. Keep activity/connection cues on `AppNavTrigger` and restore the actual opener unless focus moves into a launched surface. Tablet/desktop composition stays separate. Phone navigation uses Home for all listing modes; Tasks/Threads remain routes and palette destinations. The inline Tasks heading retains its independent plus. Phone Automations reuses workspace automation reads only while expanded, and Integrations retains a workspace settings entry even without configured links. Phone app navigation groups the built-in Quick Chat/Quick terminal actions below Home. `MobileQuickActions` shares launch and focus behavior; plugin actions and metrics retain separate slots. `MobileIntegrationsSection` opts into a local, initially collapsed disclosure only for phone app navigation; wider consumers retain expanded rendering. +- Phone hamburger buttons use `AppNavSheet` for app navigation across listings, task workbenches, and page shells. The task title opens the task picker; `ResponsiveTaskPicker` owns its dialogs above responsive layout branches, and `TaskSheetSelectionProvider` shares cancellation with the embedded task sidebar. `MobileTaskNavigationProvider` retains the inline sidebar controller and action dialogs above responsive page headers, rendering the body into the shared menu outlet. Tasks collapses in place and shares the menu scroller. `KanbanHeaderMobile` opens listing-only `MobileMenuSheet` options from the Kanban/Threads/List title dropdown; Threads saved views render inline inside that surface. `MobileListingMenuActions` supplies search in listing options. Phone app navigation groups main-toolbar, sidebar workspace, and task plugin controls in `MobilePluginNavSection`, with context labels when workspace and task controls coexist. Fallback system metrics follow navigation before Utilities. Keep activity/connection cues on `AppNavTrigger` and restore the actual opener unless focus moves into a launched surface. Tablet/desktop composition stays separate. Phone navigation uses Home for all listing modes; Tasks/Threads remain routes and palette destinations. The inline Tasks heading retains its independent plus. Phone Automations reuses workspace automation reads only while expanded, and Integrations retains a workspace settings entry even without configured links. Phone app navigation groups the built-in Quick Chat/Quick terminal actions below Home. `MobileQuickActions` shares launch and focus behavior; plugin actions and metrics retain separate slots. `MobileIntegrationsSection` opts into a local, initially collapsed disclosure only for phone app navigation; wider consumers retain expanded rendering. +- Saved phone sidebar layouts retain Home/quick actions before the task outlet and put optional tools/groups afterward in saved relative order. Built-in Automations, Canvases, and Integrations use labelled phone disclosures; icon strips belong to custom shortcut groups. The built-in integration disclosure excludes plugin links rendered as independently customizable nodes. Never persist this phone composition over the desktop layout. ## Data Flow Pattern (Critical) diff --git a/apps/web/components/app-sidebar/app-sidebar-workspace-actions.tsx b/apps/web/components/app-sidebar/app-sidebar-workspace-actions.tsx index d80c85fb76..a17b223a81 100644 --- a/apps/web/components/app-sidebar/app-sidebar-workspace-actions.tsx +++ b/apps/web/components/app-sidebar/app-sidebar-workspace-actions.tsx @@ -13,6 +13,7 @@ import { usePluginRegistry } from "@/lib/plugins/registry"; import { cn } from "@/lib/utils"; import { canvasHref, workspaceCanvasSettingsHref } from "@/lib/api/domains/canvas-api"; import { isActiveWorkspaceCanvas, useWorkspaceCanvases } from "./sections/canvases-section"; +import { useHasSavedSidebarLayout } from "@/hooks/domains/sidebar/use-sidebar-layout-navigation"; /** * Props forwarded to every plugin component registered for the @@ -44,7 +45,7 @@ export function AppSidebarWorkspaceActions(props: { className={cn( "flex shrink-0 items-center", presentation === "mobile" - ? "gap-2 [&_a]:min-h-11 [&_a]:min-w-11 [&_button]:min-h-11 [&_button]:min-w-11" + ? "min-w-0 max-w-full flex-wrap gap-2 [&_a]:min-h-11 [&_a]:min-w-11 [&_button]:min-h-11 [&_button]:min-w-11" : "gap-1", )} data-plugin-slot="sidebar-workspace-actions" @@ -56,26 +57,30 @@ export function AppSidebarWorkspaceActions(props: { } /** - * Shared phone navigation entry point for workspace-scoped plugin actions. - * The same slot is mounted in the kanban drawer and at the top of the shared - * app navigation sheet on other phone surfaces. + * Workspace canvases and optional plugin actions for navigation menus. + * Phone app navigation renders plugin actions in its dedicated Plugins section. */ export function MobileWorkspaceActionsSection({ workspaceId: providedWorkspaceId, + includePluginActions = true, }: { workspaceId?: string; + includePluginActions?: boolean; }) { const { t } = useTranslation(); const activeWorkspaceId = useAppStore((state) => state.workspaces?.activeId ?? null); const canvasesEnabled = useFeature("canvases"); + const hasSavedSidebarLayout = useHasSavedSidebarLayout(); + const showCanvases = canvasesEnabled && !hasSavedSidebarLayout; const registry = usePluginRegistry(); const pathname = usePathname(); const workspaceId = providedWorkspaceId ?? activeWorkspaceId; - const canvases = useWorkspaceCanvases(canvasesEnabled ? workspaceId : null); + const canvases = useWorkspaceCanvases(showCanvases ? workspaceId : null); const activeCanvases = canvases.filter(isActiveWorkspaceCanvas); - const hasPluginActions = registry.getSlotRegistrations("sidebar-workspace-actions").length > 0; + const hasPluginActions = + includePluginActions && registry.getSlotRegistrations("sidebar-workspace-actions").length > 0; - if (!workspaceId || (!hasPluginActions && !canvasesEnabled)) { + if (!workspaceId || (!hasPluginActions && !showCanvases)) { return null; } @@ -86,7 +91,7 @@ export function MobileWorkspaceActionsSection({ role="group" aria-label={t("common:workspace")} > - {canvasesEnabled && ( + {showCanvases && (