From 8bff59bace26caad167f2d0bc7bedebd635c550b Mon Sep 17 00:00:00 2001 From: Maksim Date: Mon, 20 Jul 2026 20:28:04 +0300 Subject: [PATCH] chore: untrack local tooling state Stop tracking Serena's memory dir and the local Claude settings file; both are per-developer state, not shared project config. Local copies are kept (git rm --cached only), and .gitignore now keeps them out of the repo going forward. --- .claude/settings.json | 75 --- .gitignore | 4 + .serena/.gitignore | 1 - .../AppSwitcher_OLD_Implementation.md | 309 --------- .../VcDropdown_Component_Usage_Analysis.md | 622 ------------------ .../ai-agent-plugin-implementation.md | 391 ----------- .../backdrop-overlay-consistency-plan.md | 28 - .../memories/blade-skeleton-teleport-bug.md | 61 -- .../circular-dependency-refactoring.md | 21 - .../mobile-apphub-tabs-implementation.md | 70 -- .serena/memories/project_overview.md | 48 -- .serena/memories/suggested_commands.md | 47 -- .serena/memories/task_completion_checklist.md | 36 - .../virto-oz-chatbot-implementation.md | 541 --------------- .../memories/virto-oz-implementation-plan.md | 204 ------ .serena/project.yml | 140 ---- 16 files changed, 4 insertions(+), 2594 deletions(-) delete mode 100644 .claude/settings.json delete mode 100644 .serena/.gitignore delete mode 100644 .serena/memories/AppSwitcher_OLD_Implementation.md delete mode 100644 .serena/memories/VcDropdown_Component_Usage_Analysis.md delete mode 100644 .serena/memories/ai-agent-plugin-implementation.md delete mode 100644 .serena/memories/backdrop-overlay-consistency-plan.md delete mode 100644 .serena/memories/blade-skeleton-teleport-bug.md delete mode 100644 .serena/memories/circular-dependency-refactoring.md delete mode 100644 .serena/memories/mobile-apphub-tabs-implementation.md delete mode 100644 .serena/memories/project_overview.md delete mode 100644 .serena/memories/suggested_commands.md delete mode 100644 .serena/memories/task_completion_checklist.md delete mode 100644 .serena/memories/virto-oz-chatbot-implementation.md delete mode 100644 .serena/memories/virto-oz-implementation-plan.md delete mode 100644 .serena/project.yml diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 713b7a8bf..000000000 --- a/.claude/settings.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "permissions": { - "allow": ["mcp__filesystem__read_text_file"] - }, - "hooks": { - "PreToolUse": [ - { - "matcher": "Edit", - "hooks": [ - { - "type": "command", - "command": "case \"$CLAUDE_FILE_PATH\" in */dist/*|*/node_modules/*|yarn.lock|*.lock) echo 'BLOCK: Cannot edit generated/lock file'; exit 1;; esac" - } - ] - }, - { - "matcher": "Write", - "hooks": [ - { - "type": "command", - "command": "case \"$CLAUDE_FILE_PATH\" in */dist/*|*/node_modules/*|yarn.lock|*.lock) echo 'BLOCK: Cannot edit generated/lock file'; exit 1;; esac" - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "Edit", - "hooks": [ - { - "type": "command", - "command": "filepath=\"$CLAUDE_FILE_PATH\"; case \"$filepath\" in *.ts|*.vue|*.js|*.tsx|*.jsx) prettier --write \"$filepath\" 2>/dev/null; eslint --fix --cache \"$filepath\" 2>/dev/null;; *.css|*.json|*.md|*.html) prettier --write \"$filepath\" 2>/dev/null;; esac" - } - ] - }, - { - "matcher": "Write", - "hooks": [ - { - "type": "command", - "command": "filepath=\"$CLAUDE_FILE_PATH\"; case \"$filepath\" in *.ts|*.vue|*.js|*.tsx|*.jsx) prettier --write \"$filepath\" 2>/dev/null; eslint --fix --cache \"$filepath\" 2>/dev/null;; *.css|*.json|*.md|*.html) prettier --write \"$filepath\" 2>/dev/null;; esac" - } - ] - }, - { - "matcher": "Edit", - "hooks": [ - { - "type": "command", - "command": "case \"$CLAUDE_FILE_PATH\" in */framework/*.ts|*/framework/*.vue) yarn typecheck --pretty 2>&1 | head -40;; esac" - } - ] - }, - { - "matcher": "Write", - "hooks": [ - { - "type": "command", - "command": "case \"$CLAUDE_FILE_PATH\" in */framework/*.ts|*/framework/*.vue) yarn typecheck --pretty 2>&1 | head -40;; esac" - } - ] - }, - { - "matcher": "Edit|Write", - "hooks": [ - { - "type": "command", - "command": "jq -r '.tool_input.file_path // .tool_response.filePath // empty' | { read -r f; case \"$f\" in */framework/*.vue|*/framework/*.ts) case \"$f\" in *.docs.md|*.stories.ts) ;; *) echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":\"[docs-keeper] You just modified a framework source file. Check if the corresponding *.docs.md and/or *.stories.ts need to be updated. Use the docs-keeper skill if needed.\"}}' ;; esac ;; esac; }", - "timeout": 5 - } - ] - } - ] - } -} diff --git a/.gitignore b/.gitignore index 9735690fc..6da9f2100 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,7 @@ package-lock.json .worktrees .planning .superpowers/ + +# Local tooling state (kept local, not tracked) +.serena/ +.claude/settings.json diff --git a/.serena/.gitignore b/.serena/.gitignore deleted file mode 100644 index 14d86ad62..000000000 --- a/.serena/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/cache diff --git a/.serena/memories/AppSwitcher_OLD_Implementation.md b/.serena/memories/AppSwitcher_OLD_Implementation.md deleted file mode 100644 index d46546736..000000000 --- a/.serena/memories/AppSwitcher_OLD_Implementation.md +++ /dev/null @@ -1,309 +0,0 @@ -# OLD App-Switcher Implementation Analysis - -## Overview -The OLD app-switcher implementation is a relatively simple, self-contained component system that manages application switching in the VcApp shell. It consists of UI components and a composable for data/logic management. - -## Directory Structure -``` -framework/shared/components/app-switcher/ -├── index.ts # Module registration & re-exports -├── components/ -│ ├── index.ts # Component barrel export -│ └── vc-app-switcher/ -│ ├── index.ts # Component export -│ └── vc-app-switcher.vue # Main UI component (SFC) -└── composables/ - ├── index.ts # Composable barrel export - └── useAppSwitcher/ - └── index.ts # Main composable logic -``` - -## Component: VcAppSwitcher -**File**: `framework/shared/components/app-switcher/components/vc-app-switcher/vc-app-switcher.vue` - -### Template Structure -```vue - -``` - -### Props -```typescript -export interface Props { - appsList: AppDescriptor[]; -} -``` - -### Emits -```typescript -interface Emits { - (event: "onClick", item: AppDescriptor): void; -} -``` - -### Methods -- **`imageUrl(url: string)`**: Passthrough function that returns the URL as-is -- **`locationHandler(url: string)`**: Determines if a URL matches the current pathname - - Cleans current pathname by removing trailing slashes - - Uses regex match to check if URL is active - - Returns boolean indicating if item is "active" -- **`switchApp(app: AppDescriptor)`**: Emits onClick event with the selected app - -### Styling -```scss -.vc-app-switcher { - @apply tw-relative tw-flex tw-shrink-0 tw-h-full; - - &__item { - @apply tw-flex tw-items-center tw-w-full tw-p-3 tw-w-full; - } - - &__item-icon { - @apply tw-w-5 tw-h-5 tw-mr-2 tw-shrink-0; - } - - &__item-title { - @apply tw-font-normal tw-text-sm tw-leading-5 tw-truncate; - color: var(--app-switcher-item-text-color); - transition: opacity 0.3s ease; - } -} -``` - -CSS Custom Properties: -- `--app-switcher-item-text-color`: Default is `var(--additional-950)` (dark text) -- `--app-switcher-height`: Default is `var(--app-bar-height)` - -## Composable: useAppSwitcher -**File**: `framework/shared/components/app-switcher/composables/useAppSwitcher/index.ts` - -### Interface -```typescript -interface IUseAppSwitcher { - readonly appsList: Ref; - getApps: () => Promise; - switchApp: (app: AppDescriptor) => void; -} -``` - -### Return Type -```typescript -{ - appsList: computed(() => appsList.value), // Computed ref (read-only) - getApps, // Async function to fetch apps - switchApp, // Function to switch app -} -``` - -### Implementation Details - -#### `getApps()` Function -- Creates an `AppsClient` instance -- Calls `client.getApps()` to fetch the app list from API -- Updates the `appsList.value` with the result -- Logs errors if fetch fails - -#### `switchApp(app: AppDescriptor)` Function -- Checks if user has permission to access the app using `hasAccess(app.permission)` -- If permitted: - - Performs navigation: `window.location.href = window.location.origin + app.relativeUrl` - - This is a hard page navigation (full page reload) -- If NOT permitted: - - Shows error notification: `"PERMISSION_MESSAGES.ACCESS_RESTRICTED"` - - Timeout: 3000ms - -### Dependencies -- `@core/api/platform`: `AppDescriptor`, `AppsClient` -- `@core/composables`: `usePermissions()` -- `@shared/components/notifications`: notification service -- `@core/plugins`: i18n -- `@core/utilities`: createLogger - -### API Integration -- Uses `AppsClient` (auto-generated from API specification) -- Calls `client.getApps()` to fetch available applications -- Each app has permission requirements that are validated - -## AppDescriptor Type (from core/api/platform.ts) -```typescript -export interface IAppDescriptor { - title?: string | undefined; - description?: string | undefined; - iconUrl?: string | undefined; - relativeUrl?: string | undefined; - permission?: string | undefined; - id?: string | undefined; -} - -export class AppDescriptor implements IAppDescriptor { - title?: string | undefined; - description?: string | undefined; - iconUrl?: string | undefined; - relativeUrl?: string | undefined; - permission?: string | undefined; - id?: string | undefined; -} -``` - -## Module Registration -**File**: `framework/shared/components/app-switcher/index.ts` - -```typescript -export const VcAppSwitcherComponent = createModule(components); -``` - -The component is registered as a Vue module and installed in the SharedModule: - -**File**: `framework/shared/index.ts` -```typescript -export const SharedModule = { - install(app: App, args: { router: Router }): void { - app - .use(VcAppSwitcherComponent) - // ... other modules - }, -}; -``` - -When installed, `VcAppSwitcher` is registered as a global component and automatically available throughout the app. - -## Integration in VcApp Shell - -### Entry Point: useShellLifecycle Hook -**File**: `framework/ui/components/organisms/vc-app/composables/useShellLifecycle.ts` - -```typescript -export function useShellLifecycle(props: { isReady: boolean }) { - // ... other composables - const { appsList, switchApp, getApps } = useAppSwitcher(); - - // Bootstrap flow: once authenticated, load apps - watch( - [() => props.isReady, isAuthenticated], - async ([isReady, isAuth]) => { - if (!isBootstrapped.value && isReady && isAuth) { - isBootstrapped.value = true; - await loadFromHistory(); - await getApps(); // <-- Loads app list on bootstrap - } - }, - { immediate: true }, - ); - - return { isAppReady, isAuthenticated, appsList, switchApp }; -} -``` - -### Flow in vc-app.vue -1. VcApp component injects `useShellLifecycle()` composable -2. Gets `appsList` and `switchApp` function -3. Passes them to layout components (DesktopLayout/MobileLayout) via props -4. Layouts pass them to AppHubPopover or as slot scopes - -### DesktopLayout Component Integration -**File**: `framework/ui/components/organisms/vc-app/_internal/layouts/DesktopLayout.vue` - -```vue - - - -``` - -The AppHubPopover component displays the app switcher interface in a popover. - -### MobileLayout Integration -**File**: `framework/ui/components/organisms/vc-app/_internal/layouts/MobileLayout.vue` - -Mobile layout uses MenuSidebar component which has an `app-switcher` slot: - -```vue - -``` - -## Key Characteristics - -### Simplicity -- Minimal component structure: one Vue component + one composable -- Single responsibility: display apps and handle switching - -### Navigation Model -- Hard navigation via `window.location.href` -- Full page reload when switching apps -- Works across domain/protocol boundaries - -### Permission-Based Access -- Each app has an optional `permission` property -- Permission is checked before allowing app switch -- Shows error notification if access denied - -### UI Integration -- Uses VcDropdown for display -- Shows icon + title for each app -- Highlights current app based on URL matching -- Minimal styling, uses CSS custom properties for customization - -### Lifecycle -- Apps are fetched during shell bootstrap (once authenticated) -- List is cached in ref and passed down component tree -- No reactive updates to app list during session - -## Exports Summary - -### From `framework/shared/components/app-switcher/index.ts` -- `VcAppSwitcherComponent`: The Vue module -- `useAppSwitcher`: The composable function -- Global component: `VcAppSwitcher` - -### Available Throughout App -Via `SharedModule` installation: -- Component: `` -- Composable: `const { appsList, getApps, switchApp } = useAppSwitcher()` - -## Current Usage Pattern -In real applications, the old implementation is NOT typically used directly. Instead: -1. Shell bootstrap calls `useAppSwitcher().getApps()` -2. `appsList` is passed to layout components -3. Layouts pass it to AppHubPopover or similar UI components -4. Custom slot implementations can override default rendering -5. User clicks trigger `switchApp(app)` which performs hard navigation diff --git a/.serena/memories/VcDropdown_Component_Usage_Analysis.md b/.serena/memories/VcDropdown_Component_Usage_Analysis.md deleted file mode 100644 index a820375c8..000000000 --- a/.serena/memories/VcDropdown_Component_Usage_Analysis.md +++ /dev/null @@ -1,622 +0,0 @@ -# VcDropdown Component - Full Usage Analysis - -## Component Overview - -**Location**: `/Users/symbot/DEV/vc-shell-main-dev/vc-shell/framework/ui/components/molecules/vc-dropdown/vc-dropdown.vue` - -**Key Props**: -- `modelValue?: boolean` - Controlled open/closed state (v-model) -- `items?: T[]` - Array of items to render -- `itemText?: (item: T) => string` - Maps item to display text -- `isItemActive?: (item: T) => boolean` - Marks active/selected item -- `floating?: boolean` (default: false) - Enable floating positioning with `@floating-ui` -- `placement?: Placement` (default: "bottom") - Floating-UI placement -- `variant?: "default" | "secondary"` (default: "default") - Visual style -- `offset?: { mainAxis?: number; crossAxis?: number }` - Floating offset -- `maxHeight?: number | string` (default: 300) - Max dropdown height -- `role?: "menu" | "listbox"` (default: "menu") - ARIA role -- `padded?: boolean` (default: true) - Compact menu padding and item backgrounds -- `closeOnClickOutside?: boolean` (default: true) -- `closeOnEscape?: boolean` (default: true) -- `closeOnSelect?: boolean` (default: false) -- `zIndex?: number` (default: 10000) - -**Key Slots**: -- `#trigger` - Custom trigger button/element (receives `{ isActive, toggle, open, close }`) -- `#item` - Custom item renderer (receives `{ item, click }`) -- `#items-container` - Custom container for items (receives `{ items, close }`) -- `#empty` - Custom empty state - -**Events**: -- `@update:modelValue` - Model value change -- `@item-click` - Item selection event -- `@open` / `@close` - Open/close lifecycle -- `@close` receives reason: `"outside" | "escape" | "action"` - -**CSS Custom Properties**: -- `--vc-dropdown-bg`: Background color (default: `var(--additional-50)`) -- `--vc-dropdown-text`: Text color (default: `var(--neutrals-950)`) -- `--vc-dropdown-border`: Border color (default: `var(--neutrals-200)`) -- `--vc-dropdown-accent`: Hover/active bg (default: `var(--neutrals-100)`) -- `--vc-dropdown-divider`: Item divider (mobile only) - ---- - -## Usage Sites (25 files) - -### 1. **notification-dropdown.vue** - Notification panel display -**File**: `/framework/shared/components/notification-dropdown/notification-dropdown.vue` -```vue - - - -``` -**Props Used**: `modelValue`, `items`, `emptyText`, `maxHeight`, `padded=false` -**Slots**: `#item` -**Notes**: -- Always open (`modelValue=true`) -- NO padding (`padded=false`) - allows full-width custom item rendering -- Auto max-height for flexible content -- Renders custom `NotificationItem` components in items - ---- - -### 2. **multilanguage-selector.vue** - Language flag selector dropdown -**File**: `/framework/shared/components/multilanguage-selector/multilanguage-selector.vue` -```vue - - - - - -``` -**Props Used**: `modelValue`, `items`, `floating`, `placement`, `offset`, `emptyText`, `isItemActive` -**Slots**: `#trigger`, `#item`, `#empty` -**Notes**: -- Floating dropdown with `bottom-end` placement -- Custom offset: `mainAxis: 10, crossAxis: -15` -- Custom trigger with image (flag) -- Custom items with image + label -- Emits `item-click` to update parent - ---- - -### 3. **generic-dropdown.vue** - Generic wrapper around VcDropdown -**File**: `/framework/shared/components/generic-dropdown/generic-dropdown.vue` -```vue - - - - - - -``` -**Props Used**: All props forwarded with transformation (modelValue becomes opened/disabled guard) -**Slots**: All slots pass-through -**Notes**: -- Pure wrapper/adapter -- Adds disability check on opened/item-click -- Emits different event names: `update:opened` instead of `update:modelValue` - ---- - -### 4. **language-selector.vue** - Settings menu language selector -**File**: `/framework/shared/components/language-selector/language-selector.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) with `SettingsMenuItem` -**Notes**: Uses VcDropdownPanel instead of VcDropdown for consistent settings panel styling - ---- - -### 5. **theme-selector.vue** - Settings menu theme selector -**File**: `/framework/shared/components/theme-selector/theme-selector.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) -**Notes**: Uses VcDropdownPanel for settings panel - ---- - -### 6. **user-dropdown-button.vue** - User account menu -**File**: `/framework/shared/components/user-dropdown-button/user-dropdown-button.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) -**Notes**: -- Desktop: Uses VcDropdownPanel with floating positioning -- Mobile: Falls back to sidebar component -- Right-placed panel (`placement="right"`, `width="260px"`) - ---- - -### 7. **vc-app-switcher.vue** - Multi-app switcher -**File**: `/framework/shared/components/app-switcher/components/vc-app-switcher/vc-app-switcher.vue` -```vue - - - -``` -**Props Used**: `modelValue=true`, `items`, `isItemActive`, `maxHeight="auto"`, `padded=false` -**Slots**: `#item` -**Notes**: -- Always open -- No padding (full-width items) -- Auto height -- Custom item with icon + title -- Active state determined by URL matching - ---- - -### 8. **TableColumnSwitcher.vue** - Table column visibility dropdown -**File**: `/framework/ui/components/organisms/vc-table/_internal/vc-table-column-switcher/vc-table-column-switcher.vue` -```vue - - - - -``` -**Props Used**: `modelValue`, `items`, `placement`, `floating`, `maxHeight` -**Slots**: `#trigger`, `#item` -**Notes**: -- Floating dropdown at bottom-end -- Max height: 40% of viewport -- Custom trigger: VcButton with icon -- Custom items with visibility checkmark - ---- - -### 9. **ToolbarDesktop.vue** - Blade toolbar more menu (desktop) -**File**: `/framework/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarDesktop.vue` -```vue - - - - -``` -**Props Used**: `modelValue`, `items`, `floating`, `placement`, `variant="secondary"` -**Slots**: `#trigger`, `#item` -**Notes**: -- Secondary variant (darker styling) -- Custom trigger with icon + optional text -- Custom items with icon + title -- Closes after selection via `handleItemClick` - ---- - -### 10. **WidgetContainerDesktop.vue** - Blade widget overflow menu (desktop) -**File**: `/framework/ui/components/organisms/vc-app/_internal/app-bar/components/WidgetContainerDesktop.vue` -```vue - - - - -``` -**Props Used**: `modelValue`, `items`, `floating`, `placement`, `variant="secondary"` -**Slots**: `#trigger`, `#item` -**Notes**: -- Renders dynamic widget components in items -- Closes on item click -- Secondary variant - ---- - -### 11. **WidgetContainerMobile.vue** - Blade widget overflow menu (mobile) -**File**: `/framework/ui/components/organisms/vc-app/_internal/app-bar/components/WidgetContainerMobile.vue` -```vue - - - - -``` -**Props Used**: `modelValue`, `items`, `floating`, `placement`, `variant="secondary"` -**Slots**: `#trigger`, `#item` -**Notes**: -- Mobile version: `placement="top-end"` (pops up from bottom) -- Icon: `lucide-chevron-up` (vs desktop's `chevron-down`) -- Same secondary variant -- Renders dynamic widget components - ---- - -### 12. **vc-breadcrumbs.vue** - Breadcrumb overflow menu -**File**: `/framework/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.vue` -```vue - - - - -``` -**Props Used**: `items`, `modelValue`, `floating`, `variant`, `placement`, `offset` -**Slots**: `#trigger`, `#item` -**Notes**: -- Breadcrumb items in overflow -- Custom offset: `{ mainAxis: 10 }` -- Trigger button with ellipsis icon -- Items are `VcBreadcrumbsItem` components - ---- - -### 13. **VcTableAdapter.vue** - Legacy table adapter -**File**: `/framework/ui/components/organisms/vc-table/VcTableAdapter.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) -**Notes**: Uses VcDropdownPanel for legacy filters panel - ---- - -### 14. **GlobalFiltersPanel.vue** - Table global filters panel -**File**: `/framework/ui/components/organisms/vc-table/components/GlobalFiltersPanel.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) -**Notes**: Uses VcDropdownPanel for consistent table panel styling with filters - ---- - -### 15. **TableColumnSwitcher.vue** - Table column switcher panel (new) -**File**: `/framework/ui/components/organisms/vc-table/components/TableColumnSwitcher.vue` -**Uses**: `VcDropdownPanel` (NOT VcDropdown directly) -**Notes**: Uses VcDropdownPanel for column visibility toggles - ---- - -### 16. **vc-dropdown-panel/index.ts** - VcDropdownPanel export -**File**: `/framework/ui/components/molecules/vc-dropdown-panel/index.ts` -**Notes**: Exports VcDropdownPanel which wraps VcDropdown concepts into a panel component - ---- - -### 17-25. Other files -- `vc-dropdown.test.ts` - Test file -- `vc-dropdown.stories.ts` - Storybook stories (see below) -- `index.ts` - Component export -- `dynamic-blade-form.vue` - Uses VcImage/VcDropdown (minor usage) -- `ui/components/molecules/index.ts` - Export barrel -- `shared/components/index.ts` - Export barrel -- `CHANGELOG.md` - Documentation - ---- - -## Storybook Stories Analysis - -**Location**: `/framework/ui/components/molecules/vc-dropdown/vc-dropdown.stories.ts` - -### Story 1: **ActionMenu** - Contextual actions with descriptions -```vue - - - - -``` -**Props**: `placement="bottom-start"`, `variant="secondary"`, `closeOnSelect=true` -**Styling**: Wide items (320px), custom buttons with icons and danger coloring - -### Story 2: **WorkspaceSwitcher** - List with selection -```vue - - - - -``` -**Props**: `role="listbox"`, `isItemActive`, `variant="default"` -**Styling**: Item sub-text, checkmark icon for active item - -### Story 3: **ScrollableList** - Long list with keyboard nav -```vue - - - - -``` -**Props**: `maxHeight="220"`, `closeOnSelect=false`, `role="listbox"` -**Styling**: Multi-line items with colored indicator dot - ---- - -## Visual Inconsistencies Identified - -### 1. **Padding/Padded Prop Usage** -- `padded=true` (default): Compact items with rounded backgrounds (4px padding container, border-radius) - - Used in: ToolbarDesktop, TableColumnSwitcher, breadcrumbs -- `padded=false`: Full-width items, no background, for rich content - - Used in: NotificationDropdown, AppSwitcher, dynamic blade form - -### 2. **Variant Styling** -- `variant="default"` (default): Lighter background (`--additional-50`) - - Used in: WorkspaceSwitcher story, language selector -- `variant="secondary"` (darker): Secondary styling - - Used in: ActionMenu story, ToolbarDesktop, WidgetContainers - -### 3. **Floating vs. Non-floating** -- `floating=false` (default): Positioned relative to trigger - - Used in: Storybook default, some internal dropdowns -- `floating=true`: Positioned with `@floating-ui` with fixed strategy - - Used in: Breadcrumbs, ToolbarDesktop, WidgetContainers, multilanguage-selector - -### 4. **Placement Variations** -- `placement="bottom"` (default) -- `placement="bottom-start"` - ActionMenu, vc-breadcrumbs -- `placement="bottom-end"` - ToolbarDesktop, WidgetContainers (desktop), multilanguage-selector -- `placement="top-end"` - WidgetContainers (mobile) - unique mobile placement -- `placement="right"` - user-dropdown-button via VcDropdownPanel -- `placement="right-start"` - language-selector, theme-selector via VcDropdownPanel - -### 5. **Max Height Variations** -- `maxHeight="300"` (default) -- `maxHeight="auto"` - NotificationDropdown, AppSwitcher (no height limit) -- `maxHeight="220"` - ScrollableList story -- `maxHeight="40%"` - TableColumnSwitcher (percentage of viewport) - -### 6. **Offset Variations** -- Default: `{ mainAxis: 0, crossAxis: 0 }` -- multilanguage-selector: `{ mainAxis: 10, crossAxis: -15 }` -- vc-breadcrumbs: `{ mainAxis: 10 }` - -### 7. **Item Rendering Patterns** -- **Simple text**: Default `itemText` callback -- **Rich content with icons**: ToolbarDesktop, breadcrumbs -- **Multi-line text**: WorkspaceSwitcher, ScrollableList stories -- **Images + text**: AppSwitcher, multilanguage-selector -- **Custom components**: WidgetContainers (dynamic component rendering) - -### 8. **Role Attribute** -- `role="menu"` (default) - For action menus -- `role="listbox"` - For selection lists (WorkspaceSwitcher, ScrollableList stories) - -### 9. **VcDropdownPanel vs VcDropdown** -Some components use **VcDropdownPanel** instead: -- language-selector, theme-selector (settings menus) -- user-dropdown-button (account menu) -- TableColumnSwitcher.vue, GlobalFiltersPanel.vue (table panels) -- VcTableAdapter legacy filters - -VcDropdownPanel adds: -- Header with title + close button -- Footer slot for buttons -- Document-level click-outside close -- Escape key handling -- Middleware for better positioning diff --git a/.serena/memories/ai-agent-plugin-implementation.md b/.serena/memories/ai-agent-plugin-implementation.md deleted file mode 100644 index 5defc36c4..000000000 --- a/.serena/memories/ai-agent-plugin-implementation.md +++ /dev/null @@ -1,391 +0,0 @@ -# AI Agent Plugin Implementation - -## Overview -A Vue plugin for integrating an AI assistant panel into VC-Shell framework applications. The plugin provides seamless communication between the VC-Shell application and an external AI agent loaded via iframe. - -## Key Features -- ENV-based configuration (`APP_AI_AGENT_URL`) -- Automatic AI button in ALL blade toolbars via wildcard "*" support -- Two operation modes: - - **List blade** - batch operations with multiple selected items - - **Details blade** - single object editing with preview -- Custom suggestions per blade -- Reactive data binding via unified `dataRef: Ref` API -- Preview state support for showing changed fields -- Built-in logging via centralized logger - -## File Structure -``` -framework/core/plugins/ai-agent/ -├── index.ts # Main plugin, install() -├── types.ts # All TypeScript interfaces and types -├── constants.ts # Constants, message types, defaults -├── README.md # Documentation -├── services/ -│ └── ai-agent-service.ts # Panel state, postMessage, context management -├── composables/ -│ ├── index.ts -│ ├── useAiAgent.ts # Panel control, toolbar button registration -│ └── useAiAgentContext.ts # Context binding + preview state -└── components/ - ├── index.ts - ├── VcAiAgentPanel.vue # Main panel component - └── _internal/ - ├── VcAiAgentHeader.vue # Panel header with close/expand buttons - └── VcAiAgentIframe.vue # Iframe container for chatbot -``` - -## Plugin Installation Flow - -### 1. Plugin Registration (index.ts) -```typescript -export const aiAgentPlugin = { - install(app: App, options: AiAgentPluginOptions = {}) { - // Get URL from env or options - const url = config.url || import.meta.env[AI_AGENT_URL_ENV_KEY] || ""; - - // Skip if no URL - if (!url) { - logger.info("AI Agent plugin skipped: no URL configured"); - return; - } - - // Merge config with defaults - const finalConfig: IAiAgentConfig = { - ...DEFAULT_AI_AGENT_CONFIG, - ...config, - url, - }; - - // Provide config via dependency injection - app.config.globalProperties.$aiAgentConfig = finalConfig; - app.provide("aiAgentConfig", finalConfig); - app.provide("aiAgentAddGlobalToolbarButton", addGlobalToolbarButton); - }, -}; -``` - -### 2. Service Initialization (provideAiAgentService in useAiAgent.ts) -Called from VcApp component to create and provide the service: -```typescript -export function provideAiAgentService(options?: ProvideAiAgentServiceOptions) { - // Create service with getters for reactive context - const service = createAiAgentService({ - userGetter: () => ({...user.value}), - bladeGetter: () => ({...lastBlade}), - localeGetter: () => languageService?.currentLocale.value, - navigateToBlade: (bladeName, param, options) => {...}, - reloadBlade: () => {...}, - initialConfig: options?.config, - }); - - // Provide service for injection - provide(AiAgentServiceKey, service); - - // Register global toolbar button with wildcard "*" - if (addGlobalToolbarButton && toolbarService) { - toolbarService.registerToolbarItem( - { id, icon, title, clickHandler: () => service.togglePanel() }, - "*", // Wildcard for all blades - ); - } - - return service; -} -``` - -### 3. Toolbar Button Registration -The toolbar service supports wildcard "*" for global buttons: -```typescript -// In toolbar-service.ts -const getToolbarItems = (bladeId: string): IToolbarItem[] => { - const normalizedBladeId = bladeId.toLowerCase(); - const bladeItems = toolbarRegistry[normalizedBladeId] || []; - const globalItems = toolbarRegistry["*"] || []; - - // Merge items, avoiding duplicates by id - const allItems = [...bladeItems]; - for (const globalItem of globalItems) { - if (!allItems.some((item) => item.id === globalItem.id)) { - allItems.push(globalItem); - } - } - return allItems; -}; -``` - -## Core Service (createAiAgentService) - -### State Management -```typescript -const panelState = ref("closed"); // "closed" | "open" | "expanded" -const config = ref({...}); -const iframeRef: ShallowRef = shallowRef(null); -const contextItems = ref[]>([]); -const contextType = ref("list"); // "list" | "details" -const contextSuggestions = ref(undefined); -``` - -### Key Methods -- `openPanel()` / `closePanel()` / `togglePanel()` - Panel state control -- `expandPanel()` / `collapsePanel()` - Size control -- `sendMessage(type, payload)` - Send message to iframe -- `onMessage(handler)` - Register message handler (returns unsubscribe) -- `_setContextData(items, type, suggestions)` - Set context from useAiAgentContext -- `_onPreviewChanges(handler)` - Register preview changes handler - -### Context Payload Building -```typescript -const buildInitContextPayload = (): IInitContextPayload => ({ - userId: userGetter()?.id || "", - locale: localeGetter(), - blade: toBladeContext(bladeGetter()), - contextType: contextType.value, - items: cloneDeep(contextItems.value), // Deep clone for postMessage - suggestions: contextSuggestions.value ? cloneDeep(contextSuggestions.value) : undefined, -}); -``` - -### Message Handling -```typescript -_handleIncomingMessage = (event: MessageEvent): void => { - // Validate origin - if (!allowedOrigins.includes("*") && !allowedOrigins.includes(event.origin)) { - return; - } - - switch (message.type) { - case "CHAT_READY": - sendRawMessage({ type: "INIT_CONTEXT", payload: buildInitContextPayload() }); - break; - case "NAVIGATE_TO_APP": - navigateToBlade(payload.bladeName, payload.param, payload.options); - break; - case "RELOAD_BLADE": - reloadBlade(); - break; - case "PREVIEW_CHANGES": - previewChangesHandlers.forEach(handler => handler(payload)); - break; - case "DOWNLOAD_FILE": - downloadFile(payload); - break; - // ... - } -}; -``` - -## useAiAgentContext Composable - -### Purpose -Connects blade data to AI agent context and handles preview state. - -### Implementation -```typescript -export function useAiAgentContext(options: UseAiAgentContextOptions): UseAiAgentContextReturn { - const { dataRef, suggestions } = options; - const service = inject(AiAgentServiceKey); - - // Determine context type: array = list, object = details - const detectedContextType = isArrayRef(dataRef) ? "list" : "details"; - - // Watch and update context on data changes - watch(dataRef, () => { - const items = normalizeToArray(dataRef.value); - service._setContextData(items, detectedContextType, suggestions); - }, { deep: true, immediate: true }); - - // Handle PREVIEW_CHANGES - service._onPreviewChanges((payload) => { - const target = getTargetForChanges(dataRef); - Object.keys(payload.data).forEach(key => { - target[key] = payload.data[key]; - }); - isPreviewActive.value = true; - changedFieldsList.value = payload.changedFields || Object.keys(payload.data); - }); - - return { - previewState: { - isActive: computed(() => isPreviewActive.value), - changedFields: computed(() => changedFieldsList.value), - }, - }; -} -``` - -## PostMessage Protocol - -### Shell → Agent -| Type | Payload | Trigger | -|------|---------|---------| -| `INIT_CONTEXT` | `{userId, locale, blade, contextType, items, suggestions}` | On `CHAT_READY` from agent | -| `UPDATE_CONTEXT` | `{blade, contextType, items, suggestions, locale}` | On context changes | - -### Agent → Shell -| Type | Payload | Action | -|------|---------|--------| -| `CHAT_READY` | - | Send `INIT_CONTEXT` | -| `PREVIEW_CHANGES` | `{data, changedFields}` | Apply preview to form | -| `APPLY_CHANGES` | `{changes}` | Apply and save changes | -| `RELOAD_BLADE` | `{clearSelection?}` | Reload current blade | -| `DOWNLOAD_FILE` | `{filename, contentType, content}` | Trigger file download | -| `NAVIGATE_TO_APP` | `{bladeName, param?, options?}` | Navigate to blade | -| `CHAT_ERROR` | `{code, message}` | Log error | - -## Configuration - -### IAiAgentConfig -```typescript -interface IAiAgentConfig { - url: string; // AI agent iframe URL - title?: string; // Panel title (default: "Virto OZ") - width?: number; // Panel width (default: 350) - expandedWidth?: number; // Expanded width (default: 500) - allowedOrigins?: string[]; // Allowed origins for postMessage (default: ["*"]) -} -``` - -### Environment Variable -```env -APP_AI_AGENT_URL=https://your-ai-agent.com -``` - -## Usage Examples - -### List Blade (Batch Operations) -```typescript -const selectedItems = ref([]); -useAiAgentContext({ dataRef: selectedItems }); - -// On table selection change -const onSelectionChanged = (items: IItem[]) => { - selectedItems.value = items; -}; -``` - -### Details Blade (Single Object) -```typescript -const item = ref({}); -const aiData = ref([]); - -// Sync single object to array -watch(item, (val) => { - aiData.value = val ? [val] : []; -}, { deep: true, immediate: true }); - -const { previewState } = useAiAgentContext({ dataRef: aiData }); - -// Use previewState.isActive and previewState.changedFields -``` - -### Custom Suggestions -```typescript -useAiAgentContext({ - dataRef: aiData, - suggestions: [ - { id: "translate", title: "Translate", icon: "translation", prompt: "Translate..." }, - { id: "optimize", title: "Optimize SEO", icon: "search", prompt: "Optimize..." }, - ], -}); -``` - -## Key Implementation Details - -### Data Serialization for postMessage -Uses `cloneDeep` from lodash-es to ensure Vue reactive proxies are fully serialized before postMessage (structured clone algorithm cannot clone proxies). - -### Reactivity in Toolbar -The toolbar uses computed property that accesses `registeredToolbarItems.length` to create reactive dependency: -```typescript -const visibleItems = computed(() => { - void registeredToolbarItems.length; // Create reactive dependency - return getToolbarItems().filter(...).sort(...); -}); -``` - -### Auto-registration of Listener -Service auto-registers message listener on creation: -```typescript -// At end of createAiAgentService -_startListening(); -``` - -### Cleanup on Unmount -useAiAgentContext cleans up on component unmount: -```typescript -onUnmounted(() => { - stopWatch(); - unsubscribe(); - service._setContextData([], "list", undefined); -}); -``` - -## Key Files Modified from Framework - -1. `framework/index.ts` - Auto-install aiAgentPlugin in VirtoShellFramework.install() -2. `framework/core/services/toolbar-service.ts` - Wildcard "*" support -3. `framework/ui/components/organisms/vc-app/vc-app.vue` - provideAiAgentService() call -4. `framework/ui/components/organisms/vc-blade/_internal/vc-blade-toolbar/vc-blade-toolbar.vue` - Reactivity fix - -## Constants (constants.ts) - -```typescript -export const DEFAULT_AI_AGENT_CONFIG: IAiAgentConfig = { - url: "", - title: "Virto OZ", - width: 350, - expandedWidth: 500, - allowedOrigins: ["*"], -}; - -export const AI_AGENT_URL_ENV_KEY = "APP_AI_AGENT_URL"; -export const AI_AGENT_TOOLBAR_BUTTON_ID = "ai-agent-toggle"; -export const AI_AGENT_TOOLBAR_BUTTON_ICON = "lucide-sparkles"; -export const AI_AGENT_TOOLBAR_BUTTON_TITLE = "AI Assistant"; - -export const SHELL_TO_CHAT_MESSAGE_TYPES = { - INIT_CONTEXT: "INIT_CONTEXT", - UPDATE_CONTEXT: "UPDATE_CONTEXT", -} as const; - -export const CHAT_TO_SHELL_MESSAGE_TYPES = { - CHAT_READY: "CHAT_READY", - NAVIGATE_TO_APP: "NAVIGATE_TO_APP", - EXPAND_IN_CHAT: "EXPAND_IN_CHAT", - RELOAD_BLADE: "RELOAD_BLADE", - PREVIEW_CHANGES: "PREVIEW_CHANGES", - APPLY_CHANGES: "APPLY_CHANGES", - DOWNLOAD_FILE: "DOWNLOAD_FILE", - SHOW_MORE: "SHOW_MORE", - CHAT_ERROR: "CHAT_ERROR", -} as const; -``` - -## Types Reference (types.ts) - -### Main Interfaces -- `IAiAgentService` - Public service interface -- `IAiAgentServiceInternal` - Extended internal interface with private methods -- `IAiAgentConfig` - Configuration interface -- `IAiAgentContext` - Full context with user, blade, items -- `IAiAgentBladeContext` - Blade info (id, name, title, param, options) -- `IAiAgentUserContext` - User info (id, userName, isAdministrator, permissions) -- `IAiAgentMessage` - Message structure (type, payload, timestamp) -- `ISuggestion` - Suggestion card (id, title, icon, iconColor, prompt) - -### Payload Interfaces -- `IInitContextPayload` - INIT_CONTEXT payload -- `IUpdateContextPayload` - UPDATE_CONTEXT payload -- `IPreviewChangesPayload` - PREVIEW_CHANGES payload (data, changedFields) -- `IApplyChangesPayload` - APPLY_CHANGES payload -- `IDownloadFilePayload` - DOWNLOAD_FILE payload (filename, contentType, content) -- `INavigateToAppPayload` - NAVIGATE_TO_APP payload (bladeName, param, options) -- `IChatErrorPayload` - CHAT_ERROR payload (code, message) - -### Type Aliases -- `AiAgentPanelState = "closed" | "open" | "expanded"` -- `AiAgentContextType = "list" | "details"` -- `AiAgentMessageType = string` -- `ShellToChatMessageType` - Union of shell-to-chat message types -- `ChatToShellMessageType` - Union of chat-to-shell message types diff --git a/.serena/memories/backdrop-overlay-consistency-plan.md b/.serena/memories/backdrop-overlay-consistency-plan.md deleted file mode 100644 index c54836cdd..000000000 --- a/.serena/memories/backdrop-overlay-consistency-plan.md +++ /dev/null @@ -1,28 +0,0 @@ -# Backdrop/Overlay Consistency Plan - -## Status: Ready to execute (tasks created, no work started) - -## Plan file -`docs/plans/2026-03-03-backdrop-overlay-consistency.md` - -## Key decisions -- **Light (white-based) overlay** is the standard for the whole project -- Global tokens go in `framework/assets/styles/theme/colors.scss` -- Pattern: `rgb(from var(...) r g b / opacity)` — already used in 5+ components -- VcSidebar must switch from dark hardcoded `rgb(15 23 42/0.38)` to light `--overlay-bg` -- ToolbarMobile must switch from `rgba(0,0,0,0.4)` to `--overlay-bg` - -## 6 tasks (all pending) -1. Add global tokens to colors.scss (overlay, shadow, surface, glass) -2. Update VcSidebar → global tokens + webkit prefixes -3. Update ToolbarMobile → global tokens + frosted glass + hover -4. Wire VcPopup → global tokens (backward-compatible) -5. Wire VcAppMenu → global tokens -6. Verify: tsc + vitest - -## Files to modify -- `framework/assets/styles/theme/colors.scss` -- `framework/ui/components/organisms/vc-sidebar/vc-sidebar.vue` -- `framework/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarMobile.vue` -- `framework/ui/components/organisms/vc-popup/vc-popup.vue` -- `framework/ui/components/organisms/vc-app/_internal/menu/VcAppMenu.vue` diff --git a/.serena/memories/blade-skeleton-teleport-bug.md b/.serena/memories/blade-skeleton-teleport-bug.md deleted file mode 100644 index a35f541ed..000000000 --- a/.serena/memories/blade-skeleton-teleport-bug.md +++ /dev/null @@ -1,61 +0,0 @@ -# Blade Skeleton + Teleport Defer Bug - -## Problem -When VcBlade switches `loading` prop true↔false, repeated TypeError occurs: -``` -TypeError: Cannot read properties of null (reading '__isMounted') -at TeleportImpl.process → patch → patchBlockChildren... -``` - -## Root Cause -`BladeHeader.vue:22` has `` for tooltip. -When `loading` toggles `v-if`/`v-else` in vc-blade.vue, Vue unmounts BladeHeader. -The deferred Teleport's `el` becomes `null`, but Vue's patcher still tries `n2.el.__isMounted`. - -This is a known Vue 3.5 issue: deferred Teleport + conditional rendering (`v-if`/`v-else`). - -## Error log prefix -`[@vc-shell/framework#use-error-handler] Captured Error: TypeError: Cannot read properties of null (reading '__isMounted')` - -## Fix Options -1. Remove `defer` from the teleport in BladeHeader.vue:22 -2. Wrap teleport in additional `v-if` guard -3. Use `` pattern instead of defer -4. Change vc-blade.vue to use `v-show` instead of `v-if`/`v-else` for header - -## Files -- `framework/ui/components/organisms/vc-blade/_internal/BladeHeader.vue` — line 22, `` -- `framework/ui/components/organisms/vc-blade/vc-blade.vue` — `v-if="loading"` / `v-else` switching -- `framework/ui/composables/useTeleportTarget` — provides teleportTarget ref - -## Skeleton Status -- BladeHeaderSkeleton.vue — rewritten, self-contained styles (own CSS, no dependency on BladeHeader.vue) -- BladeContentSkeleton.vue — simplified, removed `vc-blade__content` wrapper -- BladeToolbarSkeleton.vue — works fine, unchanged -- VcSkeleton (vc-skeleton.vue) — user simplified: removed variant/block/circle, rows-only now - - BladeHeaderSkeleton/BladeToolbarSkeleton need adaptation (they still pass variant/width/height props that no longer exist) - -## Done in this session -1. Removed `defer` from ALL 5 Teleports (BladeHeader, MultivalueDropdown, SelectDropdown, vc-tooltip, AppBarOverlay) — fixes __isMounted crash -2. In vc-blade.vue: added skeletons back with `v-show="!showSkeleton"` for BladeHeader (Teleport-safe), `v-if`/`v-else` for Toolbar/Content -3. Added `showSkeleton` computed with `isInitializing` guard to prevent flash of empty content -4. Added `loading?: boolean` prop to VcBlade Props - -## Fixed: CSS Variable Collision -- `--skeleton-bg` was overridden by `communication-app` module: `:root { --skeleton-bg: var(--additional-50) }` = white -- All skeleton CSS vars renamed from `--skeleton-*` to `--vc-skeleton-*` to prevent collisions -- BladeContentSkeleton rewritten: two-column form layout with label + input-like placeholders (not just text rows) - -## Files modified -- `framework/ui/components/organisms/vc-blade/_internal/BladeHeader.vue` — removed defer -- `framework/ui/components/molecules/vc-multivalue/_internal/MultivalueDropdown.vue` — removed defer -- `framework/ui/components/molecules/vc-select/_internal/SelectDropdown.vue` — removed defer -- `framework/ui/components/atoms/vc-tooltip/vc-tooltip.vue` — removed defer -- `framework/ui/components/organisms/vc-app/_internal/app-bar/components/AppBarOverlay.vue` — removed defer -- `framework/ui/components/organisms/vc-blade/vc-blade.vue` — skeletons + showSkeleton computed - -## Confirmed NOT the cause -- Async components / dynamic imports -- CSS sizing (percentage vs px widths) -- `defer` alone (removing it fixes crash but not visual glitch) -- `isInitializing` guard (loading prop timing is not the issue) \ No newline at end of file diff --git a/.serena/memories/circular-dependency-refactoring.md b/.serena/memories/circular-dependency-refactoring.md deleted file mode 100644 index 604b1041c..000000000 --- a/.serena/memories/circular-dependency-refactoring.md +++ /dev/null @@ -1,21 +0,0 @@ -# Circular Dependency Refactoring - -## Status -- Plan created: 2026-03-03 -- Design: `docs/plans/2026-03-03-circular-dependencies-design.md` -- Plan: `docs/plans/2026-03-03-circular-dependencies-plan.md` -- **Execution**: COMPLETED 2026-03-03 -- Baseline: 244 cycles → **0 cycles** (madge) -- 12 tasks executed via subagent-driven development -- ~140 files modified across 10 commits - -## Approach -- Approach C: `import type` + direct paths -- Barrel files preserved for public API (framework/index.ts) -- Internal imports use direct module paths -- Cross-layer type imports use `import type` -- vite-plugin-circular-dependency re-enabled in production builds - -## Remaining TODO -- ESLint `no-restricted-imports` rule to prevent barrel re-introduction -- Verify vendor-portal app production build with circular dependency plugin diff --git a/.serena/memories/mobile-apphub-tabs-implementation.md b/.serena/memories/mobile-apphub-tabs-implementation.md deleted file mode 100644 index 0d0e616c3..000000000 --- a/.serena/memories/mobile-apphub-tabs-implementation.md +++ /dev/null @@ -1,70 +0,0 @@ -# Mobile AppHub Tabs — Implementation State - -## Task -Replace inline AppHub+Menu in mobile sidebar with tabbed interface (Menu/Hub tabs) with iOS-like swipe animation. - -## Design Decision -Variant B — tabs inside sidebar. Design doc: `docs/plans/2026-03-02-mobile-apphub-tabs-design.md`. Prototypes: `docs/plans/mobile-apphub-prototypes.html`. - -## Completed -- `AppHubContent.vue` — Fixed `widgets-inner` absolute positioning in mobile mode (added `tw-relative tw-inset-auto` in `&__sections--mobile`) -- `en.json` / `de.json` — Added `MOBILE_LAYOUT.TAB_MENU` / `TAB_HUB` keys under `COMPONENTS.ORGANISMS.VC_APP.INTERNAL` - -## TODO — MobileLayout.vue Full Rewrite - -### Architecture -- Wrapper div `.mobile-layout__navmenu` fills VcScrollableContainer viewport exactly (`height: 100%`, flex column) -- Override viewport: `.vc-scrollable-container__viewport:has(> .mobile-layout__navmenu) { overflow: hidden; }` -- Tabs row: flex-shrink-0, with animated indicator bar (position: absolute, bottom: 0, width: 50%) -- Slider viewport: `flex: 1; min-height: 0; overflow: hidden;` -- Slider: `display: flex; width: 200%; height: 100%; will-change: transform;` -- Each panel: `width: 50%; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain;` - -### Conditional Visibility -```ts -const showHubTab = computed(() => widgetItems.value.length > 0 || (!props.disableAppSwitcher && props.appsList.length > 0)); -const showTabs = computed(() => showHubTab.value && !props.disableMenu); -``` -- If `!showTabs`: render menu-only or hub-only directly (no slider) -- Watch `showHubTab` — reset activeIndex to 0 when it becomes false - -### Swipe Implementation -- `activeIndex`: ref(0) — 0=menu, 1=hub -- `isDragging`: ref(false) -- `dragOffsetX`: ref(0) — pixel offset during drag -- `gestureState`: 'idle'|'pending'|'horizontal'|'vertical' — locks after 8px movement -- `sliderViewportRef`: template ref - -Touch handlers: -- `touchstart.passive`: record startX/startY/startTime, set gestureState='pending' -- `touchmove` (NOT passive): if pending and |dy|>|dx|→vertical lock; if |dx|>|dy|→horizontal lock + e.preventDefault(). If horizontal: update dragOffsetX with rubber band at boundaries -- `touchend.passive`: velocity=|deltaX|/duration. If velocity>0.3 or |deltaX|>30%: switch tab. Reset isDragging. - -Rubber band formula (iOS): `(offset * dimension) / (offset + dimension)` - -### Slider/Indicator Styles (computed) -```ts -sliderStyle = computed(() => { - const base = activeIndex.value * -50; // percentage of 200% slider - if (isDragging && viewport) { - const dragPercent = (dragOffsetX / viewport.clientWidth) * 50; - return { transform: `translateX(${base + dragPercent}%)`, transition: 'none' }; - } - return { transform: `translateX(${base}%)`, transition: 'transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)' }; -}); - -indicatorStyle = computed(() => { - const progress = activeIndex + (-dragOffsetX / containerWidth); // 0 to 1 - const clamped = Math.max(0, Math.min(1, progress)); - return { transform: `translateX(${clamped * 100}%)`, transition: isDragging ? 'none' : '...' }; -}); -``` - -### Reset -- Watch `sidebar.isMenuOpen` — reset activeIndex to 0 when sidebar closes - -### Key Files -- `MobileLayout.vue` — main implementation -- `MenuSidebar.vue` — NOT modified (navmenu slot inside VcScrollableContainer) -- `AppHubContent.vue` — already fixed (widgets-inner position) -- `useAppBarWidget` from `@core/composables` — provides widget items for badge/visibility diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md deleted file mode 100644 index c27f0d949..000000000 --- a/.serena/memories/project_overview.md +++ /dev/null @@ -1,48 +0,0 @@ -# VC-Shell Project Overview - -## Purpose -VirtoShell - Vue3 Frontend framework for VirtoCommerce back-office applications. Replacement for AngularJS-based vc-platform manager. - -## Tech Stack -- **Package Manager:** Yarn Berry (monorepo) -- **Framework:** Vue 3 -- **Build:** Vite -- **Language:** TypeScript -- **Styling:** Tailwind CSS + SCSS + CSS Variables -- **Testing:** Vitest -- **Documentation:** Storybook 9.x -- **Linting:** ESLint + Prettier - -## Project Structure -``` -vc-shell/ -├── framework/ # Core framework package (@vc-shell/framework) -│ ├── ui/ # UI components library -│ │ └── components/ # atoms, molecules, organisms -│ ├── core/ # Core types, API, composables -│ └── shared/ # Shared pages (Login, etc.) -├── apps/ # Applications (vendor-portal) -├── cli/ # CLI tools (api-client, create-vc-app, config-generator) -├── configs/ # Shared configs (ts-config, vite-config) -└── scripts/ # Build and release scripts -``` - -## Key Commands -- `yarn` - Install dependencies -- `yarn build` - Build all packages -- `yarn build-framework` - Build framework only -- `yarn lint` - Run ESLint -- `yarn storybook-serve` - Run Storybook dev server (port 6006) -- `yarn storybook-build` - Build Storybook static - -## Do NOT Touch -- `shared/modules/dynamic/` — legacy code used by client projects, will be removed in the future. Never modify. - -## Code Conventions -- Vue 3 Composition API with `