From ced62caaa00c4c2e76059b7f70ab7e9d17c5c85d Mon Sep 17 00:00:00 2001 From: Alexander Brown Date: Mon, 25 Aug 2025 14:22:36 -0700 Subject: [PATCH 01/23] ADR: Monorepo Conversion (#5199) * ADR: Monorepo Conversion * ADR: Add note about releases like `comfyui-frontend-types` --- docs/adr/0002-monorepo-conversion.md | 50 ++++++++++++++++++++++++++++ docs/adr/README.md | 1 + 2 files changed, 51 insertions(+) create mode 100644 docs/adr/0002-monorepo-conversion.md diff --git a/docs/adr/0002-monorepo-conversion.md b/docs/adr/0002-monorepo-conversion.md new file mode 100644 index 00000000000..d1ada909e46 --- /dev/null +++ b/docs/adr/0002-monorepo-conversion.md @@ -0,0 +1,50 @@ +# 2. Restructure ComfyUI_frontend as a monorepo + +Date: 2025-08-25 + +## Status + +Proposed + + + +## Context + +[Most of the context is in here](https://github.com/Comfy-Org/ComfyUI_frontend/issues/4661) + +TL;DR: As we're merging more subprojects like litegraph, devtools, and soon a fork of PrimeVue, + a monorepo structure will help a lot with code sharing and organization. + +For more information on Monorepos, check out [monorepo.tools](https://monorepo.tools/) + +## Decision + +- Swap out NPM for PNPM +- Add a workspace for the PrimeVue fork +- Move the frontend code into its own app workspace +- Longer term: Extract and reorganize common infrastructure to take advantage of the new monorepo tooling + +### Tools proposed + +[PNPM](https://pnpm.io/) and [PNPM workspaces](https://pnpm.io/workspaces) + +For monorepo management, I'd probably go with [Nx](https://nx.dev/), but I could be conviced otherwise. +There's a [whole list here](https://monorepo.tools/#tools-review) if you're interested. + +## Consequences + +### Positive + +- Adding new projects with shared dependencies becomes really easy +- Makes the process of forking and customizing projects more structured, if not strictly easier +- It *could* speed up the build and development process (not guaranteed) +- It would let us cleanly organize and release packages like `comfyui-frontend-types` + +### Negative + +- Monorepos take some getting used to +- Reviews and code contribution management has to account for the different projects' situations and constraints + + \ No newline at end of file diff --git a/docs/adr/README.md b/docs/adr/README.md index 67ef529de15..3ebf340d5d6 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -11,6 +11,7 @@ An Architecture Decision Record captures an important architectural decision mad | ADR | Title | Status | Date | |-----|-------|--------|------| | [0001](0001-merge-litegraph-into-frontend.md) | Merge LiteGraph.js into ComfyUI Frontend | Accepted | 2025-08-05 | +| [0002](0002-monorepo-conversion.md) | Restructure as a Monorepo | Proposed | 2025-08-25 | ## Creating a New ADR From 50e0e290162ed734d75bb7752ddb5450209c839b Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Mon, 25 Aug 2025 17:50:06 -0700 Subject: [PATCH 02/23] [feat] Remove obsolete FirstTimeUIMessage component (#5201) The FirstTimeUIMessage was introduced in November 2024 when the new UI became default, but after 6+ months it's no longer needed as users have adapted to the new interface. The message was confusing for new users who never experienced the old UI. Changes: - Remove FirstTimeUIMessage.vue component - Remove component usage from SettingDialogContent.vue - Remove 'firstTimeUIMessage' translation key from all locales - Keep settingStore.exists() method as it's part of the public API --- .../dialog/content/SettingDialogContent.vue | 2 -- .../content/setting/FirstTimeUIMessage.vue | 26 ------------------- src/locales/ar/main.json | 1 - src/locales/en/main.json | 1 - src/locales/es/main.json | 1 - src/locales/fr/main.json | 1 - src/locales/ja/main.json | 1 - src/locales/ko/main.json | 1 - src/locales/ru/main.json | 1 - src/locales/zh-TW/main.json | 1 - src/locales/zh/main.json | 1 - 11 files changed, 37 deletions(-) delete mode 100644 src/components/dialog/content/setting/FirstTimeUIMessage.vue diff --git a/src/components/dialog/content/SettingDialogContent.vue b/src/components/dialog/content/SettingDialogContent.vue index 9d676ed0c4e..c9c71f25aec 100644 --- a/src/components/dialog/content/SettingDialogContent.vue +++ b/src/components/dialog/content/SettingDialogContent.vue @@ -41,7 +41,6 @@ > @@ -76,7 +75,6 @@ import { flattenTree } from '@/utils/treeUtil' import ColorPaletteMessage from './setting/ColorPaletteMessage.vue' import CurrentUserMessage from './setting/CurrentUserMessage.vue' -import FirstTimeUIMessage from './setting/FirstTimeUIMessage.vue' import PanelTemplate from './setting/PanelTemplate.vue' import SettingsPanel from './setting/SettingsPanel.vue' diff --git a/src/components/dialog/content/setting/FirstTimeUIMessage.vue b/src/components/dialog/content/setting/FirstTimeUIMessage.vue deleted file mode 100644 index 82794923fdd..00000000000 --- a/src/components/dialog/content/setting/FirstTimeUIMessage.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/locales/ar/main.json b/src/locales/ar/main.json index 9dc6c4caddf..13a9d331c90 100644 --- a/src/locales/ar/main.json +++ b/src/locales/ar/main.json @@ -313,7 +313,6 @@ "feedback": "ملاحظات", "filter": "تصفية", "findIssues": "العثور على مشاكل", - "firstTimeUIMessage": "هذه هي المرة الأولى التي تستخدم فيها واجهة المستخدم الجديدة. اختر \"القائمة > استخدام القائمة الجديدة > تعطيل\" لاستعادة الواجهة القديمة.", "frontendNewer": "إصدار الواجهة الأمامية {frontendVersion} قد لا يكون متوافقاً مع الإصدار الخلفي {backendVersion}.", "frontendOutdated": "إصدار الواجهة الأمامية {frontendVersion} قديم. يتطلب الإصدار الخلفي {requiredVersion} أو أحدث.", "goToNode": "الانتقال إلى العقدة", diff --git a/src/locales/en/main.json b/src/locales/en/main.json index ecc17b17cd1..c2118a286be 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -5,7 +5,6 @@ "empty": "Empty", "noWorkflowsFound": "No workflows found.", "comingSoon": "Coming Soon", - "firstTimeUIMessage": "This is the first time you use the new UI. Choose \"Menu > Use New Menu > Disabled\" to restore the old UI.", "download": "Download", "import": "Import", "loadAllFolders": "Load All Folders", diff --git a/src/locales/es/main.json b/src/locales/es/main.json index 817e1a4a0be..0e80fc9a58d 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -313,7 +313,6 @@ "feedback": "Retroalimentación", "filter": "Filtrar", "findIssues": "Encontrar problemas", - "firstTimeUIMessage": "Esta es la primera vez que usas la nueva interfaz. Elige \"Menú > Usar nuevo menú > Desactivado\" para restaurar la antigua interfaz.", "frontendNewer": "La versión del frontend {frontendVersion} puede no ser compatible con la versión del backend {backendVersion}.", "frontendOutdated": "La versión del frontend {frontendVersion} está desactualizada. El backend requiere la versión {requiredVersion} o superior.", "goToNode": "Ir al nodo", diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index a60b7838558..db878ecf85a 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -313,7 +313,6 @@ "feedback": "Commentaires", "filter": "Filtrer", "findIssues": "Trouver des problèmes", - "firstTimeUIMessage": "C'est la première fois que vous utilisez la nouvelle interface utilisateur. Choisissez \"Menu > Utiliser le nouveau menu > Désactivé\" pour restaurer l'ancienne interface utilisateur.", "frontendNewer": "La version du frontend {frontendVersion} peut ne pas être compatible avec la version du backend {backendVersion}.", "frontendOutdated": "La version du frontend {frontendVersion} est obsolète. Le backend requiert la version {requiredVersion} ou supérieure.", "goToNode": "Aller au nœud", diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 581b05e4bf9..c6a9608b7c9 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -313,7 +313,6 @@ "feedback": "フィードバック", "filter": "フィルタ", "findIssues": "問題を見つける", - "firstTimeUIMessage": "新しいUIを初めて使用しています。「メニュー > 新しいメニューを使用 > 無効」を選択することで古いUIに戻すことが可能です。", "frontendNewer": "フロントエンドのバージョン {frontendVersion} はバックエンドのバージョン {backendVersion} と互換性がない可能性があります。", "frontendOutdated": "フロントエンドのバージョン {frontendVersion} は古くなっています。バックエンドは {requiredVersion} 以上が必要です。", "goToNode": "ノードに移動", diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index a4569456141..9e1ba185784 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -313,7 +313,6 @@ "feedback": "피드백", "filter": "필터", "findIssues": "문제 찾기", - "firstTimeUIMessage": "새 UI를 처음 사용합니다. \"메뉴 > 새 메뉴 사용 > 비활성화\"를 선택하여 이전 UI로 복원하세요.", "frontendNewer": "프론트엔드 버전 {frontendVersion}이(가) 백엔드 버전 {backendVersion}과(와) 호환되지 않을 수 있습니다.", "frontendOutdated": "프론트엔드 버전 {frontendVersion}이(가) 오래되었습니다. 백엔드는 {requiredVersion} 이상이 필요합니다.", "goToNode": "노드로 이동", diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index 57cb8d5f297..c0c4df41e08 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -313,7 +313,6 @@ "feedback": "Обратная связь", "filter": "Фильтр", "findIssues": "Найти проблемы", - "firstTimeUIMessage": "Вы впервые используете новый интерфейс. Выберите \"Меню > Использовать новое меню > Отключено\", чтобы восстановить старый интерфейс.", "frontendNewer": "Версия интерфейса {frontendVersion} может быть несовместима с версией сервера {backendVersion}.", "frontendOutdated": "Версия интерфейса {frontendVersion} устарела. Требуется версия не ниже {requiredVersion} для работы с сервером.", "goToNode": "Перейти к ноде", diff --git a/src/locales/zh-TW/main.json b/src/locales/zh-TW/main.json index 28ad3d0ad05..d8d3dc1e761 100644 --- a/src/locales/zh-TW/main.json +++ b/src/locales/zh-TW/main.json @@ -313,7 +313,6 @@ "feedback": "意見回饋", "filter": "篩選", "findIssues": "尋找問題", - "firstTimeUIMessage": "這是您第一次使用新介面。若要返回舊介面,請前往「選單」>「使用新介面」>「關閉」。", "frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不相容。", "frontendOutdated": "前端版本 {frontendVersion} 已過時。後端需要 {requiredVersion} 或更高版本。", "goToNode": "前往節點", diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index f522fe87aa7..2a0482654d1 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -313,7 +313,6 @@ "feedback": "反馈", "filter": "过滤", "findIssues": "查找问题", - "firstTimeUIMessage": "这是您第一次使用新界面。选择 \"菜单 > 使用新菜单 > 禁用\" 来恢复旧界面。", "frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不相容。", "frontendOutdated": "前端版本 {frontendVersion} 已过时。后端需要 {requiredVersion} 或更高版本。", "goToNode": "转到节点", From 7d6e252814851dc1d59ec6293a6b29f8ae0877f4 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Mon, 25 Aug 2025 17:55:47 -0700 Subject: [PATCH 03/23] [feat] improve custom icon build script with TypeScript and error handling (#5202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Convert customIconCollection.js to TypeScript with proper interfaces - Add comprehensive SVG validation and error handling - Implement graceful failure - malformed icons don't break builds - Remove verbose logging, keep only errors/warnings - Update documentation in README.md, CONTRIBUTING.md, icons/README.md - Add missing @iconify/tailwind dependency 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- CONTRIBUTING.md | 2 +- build/customIconCollection.js | 29 ---------- build/customIconCollection.ts | 100 ++++++++++++++++++++++++++++++++++ package-lock.json | 1 - src/assets/icons/README.md | 25 ++++++++- tailwind.config.js | 2 +- 6 files changed, 125 insertions(+), 34 deletions(-) delete mode 100644 build/customIconCollection.js create mode 100644 build/customIconCollection.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c25dc96741d..8280246b780 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -265,7 +265,7 @@ The project supports three types of icons, all with automatic imports (no manual 2. **Iconify Icons** - 200,000+ icons from various libraries: ``, `` 3. **Custom Icons** - Your own SVG icons: `` -Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/`. +Icons are powered by the unplugin-icons system, which automatically discovers and imports icons as Vue components. Custom icons are stored in `src/assets/icons/custom/` and processed by `build/customIconCollection.ts` with automatic validation. For detailed instructions and code examples, see [src/assets/icons/README.md](src/assets/icons/README.md). diff --git a/build/customIconCollection.js b/build/customIconCollection.js deleted file mode 100644 index ae52b97ae0c..00000000000 --- a/build/customIconCollection.js +++ /dev/null @@ -1,29 +0,0 @@ -import { readFileSync, readdirSync } from 'fs' -import { join } from 'path' -import { dirname } from 'path' -import { fileURLToPath } from 'url' - -const fileName = fileURLToPath(import.meta.url) -const dirName = dirname(fileName) -const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom') - -// Create an Iconify collection for custom icons -export const iconCollection = { - prefix: 'comfy', - icons: {}, - width: 16, - height: 16 -} - -// Read all SVG files from the custom icons directory -const files = readdirSync(customIconsPath) -files.forEach((file) => { - if (file.endsWith('.svg')) { - const name = file.replace('.svg', '') - const content = readFileSync(join(customIconsPath, file), 'utf-8') - - iconCollection.icons[name] = { - body: content - } - } -}) diff --git a/build/customIconCollection.ts b/build/customIconCollection.ts new file mode 100644 index 00000000000..f2d823ed5d8 --- /dev/null +++ b/build/customIconCollection.ts @@ -0,0 +1,100 @@ +import { existsSync, readFileSync, readdirSync } from 'fs' +import { join } from 'path' +import { dirname } from 'path' +import { fileURLToPath } from 'url' + +const fileName = fileURLToPath(import.meta.url) +const dirName = dirname(fileName) +const customIconsPath = join(dirName, '..', 'src', 'assets', 'icons', 'custom') + +// Iconify collection structure +interface IconifyIcon { + body: string + width?: number + height?: number +} + +interface IconifyCollection { + prefix: string + icons: Record + width?: number + height?: number +} + +// Create an Iconify collection for custom icons +export const iconCollection: IconifyCollection = { + prefix: 'comfy', + icons: {}, + width: 16, + height: 16 +} + +/** + * Validates that an SVG file contains valid SVG content + */ +function validateSvgContent(content: string, filename: string): void { + if (!content.trim()) { + throw new Error(`Empty SVG file: ${filename}`) + } + + if (!content.includes(' tag): ${filename}`) + } + + // Basic XML structure validation + const openTags = (content.match(/]*>/g) || []).length + const closeTags = (content.match(/<\/svg>/g) || []).length + + if (openTags !== closeTags) { + throw new Error(`Malformed SVG file (mismatched svg tags): ${filename}`) + } +} + +/** + * Loads custom SVG icons from the icons directory + */ +function loadCustomIcons(): void { + if (!existsSync(customIconsPath)) { + console.warn(`Custom icons directory not found: ${customIconsPath}`) + return + } + + try { + const files = readdirSync(customIconsPath) + const svgFiles = files.filter((file) => file.endsWith('.svg')) + + if (svgFiles.length === 0) { + console.warn('No SVG files found in custom icons directory') + return + } + + svgFiles.forEach((file) => { + const name = file.replace('.svg', '') + const filePath = join(customIconsPath, file) + + try { + const content = readFileSync(filePath, 'utf-8') + validateSvgContent(content, file) + + iconCollection.icons[name] = { + body: content + } + } catch (error) { + console.error( + `Failed to load custom icon ${file}:`, + error instanceof Error ? error.message : error + ) + // Continue loading other icons instead of failing the entire build + } + }) + } catch (error) { + console.error( + 'Failed to read custom icons directory:', + error instanceof Error ? error.message : error + ) + // Don't throw here - allow build to continue without custom icons + } +} + +// Load icons when this module is imported +loadCustomIcons() diff --git a/package-lock.json b/package-lock.json index 2031b3341d2..2193dd5fc41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2344,7 +2344,6 @@ "resolved": "https://registry.npmjs.org/@iconify/tailwind/-/tailwind-1.2.0.tgz", "integrity": "sha512-KgpIHWOTcRYw1XcoUqyNSrmYyfLLqZYu3AmP8zdfLk0F5TqRO8YerhlvlQmGfn7rJXgPeZN569xPAJnJ53zZxA==", "dev": true, - "license": "MIT", "dependencies": { "@iconify/types": "^2.0.0" }, diff --git a/src/assets/icons/README.md b/src/assets/icons/README.md index cd92f1ddff3..b01a3e3ef6f 100644 --- a/src/assets/icons/README.md +++ b/src/assets/icons/README.md @@ -247,9 +247,29 @@ Icons are automatically imported using `unplugin-icons` - no manual imports need ### Configuration -The icon system is configured in `vite.config.mts`: +The icon system has two layers: + +1. **Build-time Processing** (`build/customIconCollection.ts`): + - Scans `src/assets/icons/custom/` for SVG files + - Validates SVG content and structure + - Creates Iconify collection for Tailwind CSS + - Provides error handling for malformed files + +2. **Vite Runtime** (`vite.config.mts`): + - Enables direct SVG import as Vue components + - Supports dynamic icon loading ```typescript +// Build script creates Iconify collection +export const iconCollection: IconifyCollection = { + prefix: 'comfy', + icons: { + 'workflow': { body: '...' }, + 'node': { body: '...' } + } +} + +// Vite configuration for component-based usage Icons({ compiler: 'vue3', customCollections: { @@ -271,8 +291,9 @@ Icons are fully typed. If TypeScript doesn't recognize a new custom icon: ### Icon Not Showing 1. **Check filename**: Must be kebab-case without special characters 2. **Restart dev server**: Required after adding new icons -3. **Verify SVG**: Ensure it's valid SVG syntax +3. **Verify SVG**: Ensure it's valid SVG syntax (build script validates automatically) 4. **Check console**: Look for Vue component resolution errors +5. **Build script errors**: Check console during build - malformed SVGs are logged but don't break builds ### Icon Wrong Color - Replace hardcoded colors with `currentColor` diff --git a/tailwind.config.js b/tailwind.config.js index 19cb6507dee..9c809eaa809 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,7 @@ /** @type {import('tailwindcss').Config} */ import { addDynamicIconSelectors } from '@iconify/tailwind' -import { iconCollection } from './build/customIconCollection.js' +import { iconCollection } from './build/customIconCollection.ts' export default { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], From 8646ca416210acd12618d31bffe4cb042b1c6607 Mon Sep 17 00:00:00 2001 From: snomiao Date: Wed, 27 Aug 2025 00:25:35 +0800 Subject: [PATCH 04/23] [ci] Complete implementation of safe-to-fail CI steps (#5210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ci] Make Playwright deploy step safe to fail Add continue-on-error: true to Deploy to Cloudflare Pages step to prevent Cloudflare API issues from blocking essential testing processes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * [ci] Make lint-and-format comment steps safe to fail Add continue-on-error: true to PR comment steps in lint workflow: - Comment on PR about auto-fix (line 63) - Comment on PR about manual fix needed (line 76) This prevents GitHub API permission errors from blocking essential linting processes while maintaining comment functionality. --------- Co-authored-by: Claude --- .github/workflows/lint-and-format.yaml | 2 ++ .github/workflows/test-ui.yaml | 1 + 2 files changed, 3 insertions(+) diff --git a/.github/workflows/lint-and-format.yaml b/.github/workflows/lint-and-format.yaml index 5d9a50d834f..d3b1635b215 100644 --- a/.github/workflows/lint-and-format.yaml +++ b/.github/workflows/lint-and-format.yaml @@ -60,6 +60,7 @@ jobs: - name: Comment on PR about auto-fix if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name == github.repository + continue-on-error: true uses: actions/github-script@v7 with: script: | @@ -72,6 +73,7 @@ jobs: - name: Comment on PR about manual fix needed if: steps.verify-changed-files.outputs.changed == 'true' && github.event.pull_request.head.repo.full_name != github.repository + continue-on-error: true uses: actions/github-script@v7 with: script: | diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml index a41964d7fa5..dff51c03ee9 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/test-ui.yaml @@ -182,6 +182,7 @@ jobs: - name: Deploy to Cloudflare Pages (${{ matrix.browser }}) id: cloudflare-deploy if: always() + continue-on-error: true run: | # Retry logic for wrangler deploy (3 attempts) RETRY_COUNT=0 From 74b61ecfdf748772adb3d3735627b335992b76ec Mon Sep 17 00:00:00 2001 From: Sidharth Date: Wed, 27 Aug 2025 00:55:32 +0530 Subject: [PATCH 05/23] feat: Add dropdown list for additional tabs (#5046) * feat: Add dropdown list for additional tabs * fix: workflow menu and tabs styles --- .../topbar/WorkflowOverflowMenu.vue | 47 +++++++++++++++++++ src/components/topbar/WorkflowTabs.vue | 9 +++- src/locales/en/main.json | 3 +- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/components/topbar/WorkflowOverflowMenu.vue diff --git a/src/components/topbar/WorkflowOverflowMenu.vue b/src/components/topbar/WorkflowOverflowMenu.vue new file mode 100644 index 00000000000..042aeed24f1 --- /dev/null +++ b/src/components/topbar/WorkflowOverflowMenu.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/topbar/WorkflowTabs.vue b/src/components/topbar/WorkflowTabs.vue index c9a8ac4f6b6..d4aad2c3e3b 100644 --- a/src/components/topbar/WorkflowTabs.vue +++ b/src/components/topbar/WorkflowTabs.vue @@ -16,7 +16,7 @@ ref="scrollPanelRef" class="overflow-hidden no-drag" :pt:content="{ - class: 'p-0 w-full', + class: 'p-0 w-full flex', onwheel: handleWheel }" pt:bar-x="h-1" @@ -48,6 +48,11 @@ :disabled="!rightArrowEnabled" @mousedown="whileMouseDown($event, () => scroll(1))" /> +