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 && (
+ {t("common:loading")} +
+ )} + {error && ( +{t("common:requestFailed")}
+ ++ {t("common:loading")} +
+ )} + {(catalog.error || activityError) && ( ++ {catalog.error + ? t("automations:failedToLoadAutomations") + : t("automations:failedToLoadAutomationActivity")} +
+ +