Compare commits

...

7 Commits

Author SHA1 Message Date
Benjamin Lu
0ba1d1127c Add right side panel 2025-07-02 17:55:58 -04:00
Benjamin Lu
ed5b371175 feat: Add rightSidebarTabStore for managing right sidebar visibility 2025-07-02 17:26:41 -04:00
Benjamin Lu
943bf70326 Remove references to Comfy.Sidebar.Location 2025-07-02 17:14:26 -04:00
Benjamin Lu
7c95a8d466 Delete plan... 2025-07-02 16:58:12 -04:00
Benjamin Lu
3ec765a07b deprecate Comfy.Sidebar.Location setting ID 2025-07-02 16:29:41 -04:00
Benjamin Lu
11f342309e Add plan/progress to Claude.md 2025-07-02 16:28:50 -04:00
Benjamin Lu
f30eba1c12 Empty the existing claude.md 2025-07-02 14:39:46 -04:00
13 changed files with 115 additions and 122 deletions

View File

@@ -1,58 +0,0 @@
- use `npm run` to see what commands are available
- For component communication, prefer Vue's event-based pattern (emit/@event-name) for state changes and notifications; use defineExpose with refs only for imperative operations that need direct control (like form.validate(), modal.open(), or editor.focus()); events promote loose coupling and are better for reusable components, while exposed methods are acceptable for tightly-coupled component pairs or when wrapping third-party libraries that require imperative APIs
- After making code changes, follow this general process: (1) Create unit tests, component tests, browser tests (if appropriate for each), (2) run unit tests, component tests, and browser tests until passing, (3) run typecheck, lint, format (with prettier) -- you can use `npm run` command to see the scripts available, (4) check if any READMEs (including nested) or documentation needs to be updated, (5) Decide whether the changes are worth adding new content to the external documentation for (or would requires changes to the external documentation) at https://docs.comfy.org, then present your suggestion
- When referencing PrimeVue, you can get all the docs here: https://primevue.org. Do this instead of making up or inferring names of Components
- When trying to set tailwind classes for dark theme, use "dark-theme:" prefix rather than "dark:"
- Never add lines to PR descriptions or commit messages that say "Generated with Claude Code"
- When making PR names and commit messages, if you are going to add a prefix like "docs:", "feat:", "bugfix:", use square brackets around the prefix term and do not use a colon (e.g., should be "[docs]" rather than "docs:").
- When I reference GitHub Repos related to Comfy-Org, you should proactively fetch or read the associated information in the repo. To do so, you should exhaust all options: (1) Check if we have a local copy of the repo, (2) Use the GitHub API to fetch the information; you may want to do this IN ADDITION to the other options, especially for reading specific branches/PRs/comments/reviews/metadata, and (3) curl the GitHub website and parse the html or json responses
- For information about ComfyUI, ComfyUI_frontend, or ComfyUI-Manager, you can web search or download these wikis: https://deepwiki.com/Comfy-Org/ComfyUI-Manager, https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview, https://deepwiki.com/comfyanonymous/ComfyUI/2-core-architecture
- If a question/project is related to Comfy-Org, Comfy, or ComfyUI ecosystem, you should proactively use the Comfy docs to answer the question. The docs may be referenced with URLs like https://docs.comfy.org
- When operating inside a repo, check for README files at key locations in the repo detailing info about the contents of that folder. E.g., top-level key folders like tests-ui, browser_tests, composables, extensions/core, stores, services often have their own README.md files. When writing code, make sure to frequently reference these README files to understand the overall architecture and design of the project. Pay close attention to the snippets to learn particular patterns that seem to be there for a reason, as you should emulate those.
- Prefer running single tests, and not the whole test suite, for performance
- If using a lesser known or complex CLI tool, run the --help to see the documentation before deciding what to run, even if just for double-checking or verifying things.
- IMPORTANT: the most important goal when writing code is to create clean, best-practices, sustainable, and scalable public APIs and interfaces. Our app is used by thousands of users and we have thousands of mods/extensions that are constantly changing and updating; and we are also always updating. That's why it is IMPORTANT that we design systems and write code that follows practices of domain-driven design, object-oriented design, and design patterns (such that you can assure stability while allowing for all components around you to change and evolve). We ABSOLUTELY prioritize clean APIs and public interfaces that clearly define and restrict how/what the mods/extensions can access.
- If any of these technologies are referenced, you can proactively read their docs at these locations: https://primevue.org/theming, https://primevue.org/forms/, https://www.electronjs.org/docs/latest/api/browser-window, https://vitest.dev/guide/browser/, https://atlassian.design/components/pragmatic-drag-and-drop/core-package/drop-targets/, https://playwright.dev/docs/api/class-test, https://playwright.dev/docs/api/class-electron, https://www.algolia.com/doc/api-reference/rest-api/, https://pyav.org/docs/develop/cookbook/basics.html
- IMPORTANT: Never add Co-Authored by Claude or any reference to Claude or Claude Code in commit messages, PR descriptions, titles, or any documentation whatsoever
- The npm script to type check is called "typecheck" NOT "type check"
- Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
- when we are solving an issue we know the link/number for, we should add "Fixes #n" (where n is the issue number) to the PR description.
- Never write css if you can accomplish the same thing with tailwind utility classes
- Utilize ref and reactive for reactive state
- Implement computed properties with computed()
- Use watch and watchEffect for side effects
- Implement lifecycle hooks with onMounted, onUpdated, etc.
- Utilize provide/inject for dependency injection
- Use vue 3.5 style of default prop declaration. Do not define a `props` variable; instead, destructure props. Since vue 3.5, destructuring props does not strip them of reactivity.
- Use Tailwind CSS for styling
- Leverage VueUse functions for performance-enhancing styles
- Use lodash for utility functions
- Implement proper props and emits definitions
- Utilize Vue 3's Teleport component when needed
- Use Suspense for async components
- Implement proper error handling
- Follow Vue 3 style guide and naming conventions
- IMPORTANT: Use vue-i18n for ALL user-facing strings - no hard-coded text in services/utilities. Place new translation entries in src/locales/en/main.json
- Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard.
- DO NOT use deprecated PrimeVue components. Use these replacements instead:
* `Dropdown` → Use `Select` (import from 'primevue/select')
* `OverlayPanel` → Use `Popover` (import from 'primevue/popover')
* `Calendar` → Use `DatePicker` (import from 'primevue/datepicker')
* `InputSwitch` → Use `ToggleSwitch` (import from 'primevue/toggleswitch')
* `Sidebar` → Use `Drawer` (import from 'primevue/drawer')
* `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled
* `TabMenu` → Use `Tabs` without panels
* `Steps` → Use `Stepper` without panels
* `InlineMessage` → Use `Message` component
* Use `api.apiURL()` for all backend API calls and routes
- Actual API endpoints like /prompt, /queue, /view, etc.
- Image previews: `api.apiURL('/view?...')`
- Any backend-generated content or dynamic routes
* Use `api.fileURL()` for static files served from the public folder:
- Templates: `api.fileURL('/templates/default.json')`
- Extensions: `api.fileURL(extensionPath)` for loading JS modules
- Any static assets that exist in the public directory
- When implementing code that outputs raw HTML (e.g., using v-html directive), always ensure dynamic content has been properly sanitized with DOMPurify or validated through trusted sources. Prefer Vue templates over v-html when possible.
- For any async operations (API calls, timers, etc), implement cleanup/cancellation in component unmount to prevent memory leaks
- Extract complex template conditionals into separate components or computed properties
- Error messages should be actionable and user-friendly (e.g., "Failed to load data. Please refresh the page." instead of "Unknown error")

View File

@@ -8,7 +8,6 @@
>
<SplitterPanel
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'left'"
class="side-bar-panel"
:min-size="10"
:size="20"
@@ -32,16 +31,6 @@
</SplitterPanel>
</Splitter>
</SplitterPanel>
<SplitterPanel
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'right'"
class="side-bar-panel"
:min-size="10"
:size="20"
>
<slot name="side-bar-panel" />
</SplitterPanel>
</Splitter>
</template>
@@ -55,9 +44,6 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
const settingStore = useSettingStore()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const unifiedWidth = computed(() =>
settingStore.get('Comfy.Sidebar.UnifiedWidth')

View File

@@ -40,6 +40,7 @@
<SelectionToolbox />
</SelectionOverlay>
<DomWidgets />
<RightSideToolbar />
</template>
</template>
@@ -58,6 +59,7 @@ import SelectionOverlay from '@/components/graph/SelectionOverlay.vue'
import SelectionToolbox from '@/components/graph/SelectionToolbox.vue'
import TitleEditor from '@/components/graph/TitleEditor.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
import RightSideToolbar from '@/components/sidebar/RightSideToolbar.vue'
import SideToolbar from '@/components/sidebar/SideToolbar.vue'
import SecondRowWorkflowTabs from '@/components/topbar/SecondRowWorkflowTabs.vue'
import { useChainCallback } from '@/composables/functional/useChainCallback'

View File

@@ -0,0 +1,64 @@
<template>
<teleport to=".comfyui-body-right">
<div class="right-sidebar-container" :class="{ hidden: !isVisible }">
<div class="right-sidebar-header">
<h3></h3>
<Button
icon="pi pi-times"
text
severity="secondary"
size="small"
@click="rightSidebarTabStore.hide()"
/>
</div>
<div class="right-sidebar-content">
<!-- Content will go here -->
<p></p>
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed } from 'vue'
import { useRightSidebarTabStore } from '@/stores/workspace/rightSidebarTabStore'
const rightSidebarTabStore = useRightSidebarTabStore()
const isVisible = computed(() => rightSidebarTabStore.isVisible)
</script>
<style scoped>
.right-sidebar-container {
display: flex;
flex-direction: column;
width: 300px;
height: 100%;
background-color: var(--comfy-menu-bg);
border-left: 1px solid var(--border-color);
}
.right-sidebar-container.hidden {
display: none;
}
.right-sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.right-sidebar-header h3 {
margin: 0;
font-size: 1rem;
}
.right-sidebar-content {
flex: 1;
padding: 1rem;
overflow-y: auto;
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<teleport :to="teleportTarget">
<teleport to=".comfyui-body-left">
<nav class="side-tool-bar-container" :class="{ 'small-sidebar': isSmall }">
<SidebarIcon
v-for="tab in tabs"
@@ -47,12 +47,6 @@ const workspaceStore = useWorkspaceStore()
const settingStore = useSettingStore()
const userStore = useUserStore()
const teleportTarget = computed(() =>
settingStore.get('Comfy.Sidebar.Location') === 'left'
? '.comfyui-body-left'
: '.comfyui-body-right'
)
const isSmall = computed(
() => settingStore.get('Comfy.Sidebar.Size') === 'small'
)

View File

@@ -12,10 +12,8 @@
<Teleport to="#graph-canvas-container">
<div
v-if="isHelpCenterVisible"
class="help-center-popup"
class="help-center-popup sidebar-left"
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': sidebarSize === 'small'
}"
>
@@ -26,9 +24,8 @@
<!-- Release Notification Toast positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<ReleaseNotificationToast
class="sidebar-left"
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': sidebarSize === 'small'
}"
/>
@@ -37,9 +34,8 @@
<!-- WhatsNew Popup positioned within canvas area -->
<Teleport to="#graph-canvas-container">
<WhatsNewPopup
class="sidebar-left"
:class="{
'sidebar-left': sidebarLocation === 'left',
'sidebar-right': sidebarLocation === 'right',
'small-sidebar': sidebarSize === 'small'
}"
/>
@@ -73,10 +69,6 @@ const releaseStore = useReleaseStore()
const { shouldShowRedDot } = storeToRefs(releaseStore)
const isHelpCenterVisible = ref(false)
const sidebarLocation = computed(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const sidebarSize = computed(() => settingStore.get('Comfy.Sidebar.Size'))
const toggleHelpCenter = () => {
@@ -121,10 +113,6 @@ onMounted(async () => {
left: 1rem;
}
.help-center-popup.sidebar-right {
right: 1rem;
}
@keyframes slideInUp {
from {
opacity: 0;

View File

@@ -31,7 +31,6 @@ import {
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import { ComfyModelDef } from '@/stores/modelStore'
import { useSettingStore } from '@/stores/settingStore'
import { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
import ModelPreview from './ModelPreview.vue'
@@ -62,11 +61,6 @@ const modelPreviewStyle = ref<CSSProperties>({
left: '0px'
})
const settingStore = useSettingStore()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const handleModelHover = async () => {
const hoverTarget = modelContentElement.value
if (!hoverTarget) return
@@ -80,11 +74,7 @@ const handleModelHover = async () => {
previewHeight > availableSpaceBelow
? `${Math.max(0, targetRect.top - (previewHeight - availableSpaceBelow) - 20)}px`
: `${targetRect.top - 40}px`
if (sidebarLocation.value === 'left') {
modelPreviewStyle.value.left = `${targetRect.right}px`
} else {
modelPreviewStyle.value.left = `${targetRect.left - 400}px`
}
modelPreviewStyle.value.left = `${targetRect.right}px`
await modelDef.value.load()
}

View File

@@ -58,7 +58,6 @@ import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
import NodePreview from '@/components/node/NodePreview.vue'
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import { useSettingStore } from '@/stores/settingStore'
import { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
const props = defineProps<{
@@ -72,11 +71,6 @@ const nodeBookmarkStore = useNodeBookmarkStore()
const isBookmarked = computed(() =>
nodeBookmarkStore.isBookmarked(nodeDef.value)
)
const settingStore = useSettingStore()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const toggleBookmark = async () => {
await nodeBookmarkStore.toggleBookmark(nodeDef.value)
}
@@ -101,11 +95,7 @@ const handleNodeHover = async () => {
previewHeight > availableSpaceBelow
? `${Math.max(0, targetRect.top - (previewHeight - availableSpaceBelow) - 20)}px`
: `${targetRect.top - 40}px`
if (sidebarLocation.value === 'left') {
nodePreviewStyle.value.left = `${targetRect.right}px`
} else {
nodePreviewStyle.value.left = `${targetRect.left - 400}px`
}
nodePreviewStyle.value.left = `${targetRect.right}px`
}
const container = ref<HTMLElement | null>(null)

View File

@@ -82,9 +82,4 @@ watch(
() => nextTick(updateToastPosition),
{ immediate: true }
)
watch(
() => settingStore.get('Comfy.Sidebar.Location'),
() => nextTick(updateToastPosition),
{ immediate: true }
)
</script>

View File

@@ -18,6 +18,19 @@
<Actionbar />
<CurrentUserButton class="flex-shrink-0" />
<BottomPanelToggleButton class="flex-shrink-0" />
<Button
v-tooltip="{ value: 'Toggle Right Sidebar', showDelay: 300 }"
class="flex-shrink-0"
:icon="
rightSidebarTabStore.isVisible
? 'pi pi-angle-double-right'
: 'pi pi-angle-double-left'
"
severity="secondary"
text
aria-label="Toggle Right Sidebar"
@click="rightSidebarTabStore.toggleVisibility()"
/>
<Button
v-tooltip="{ value: $t('menu.hideMenu'), showDelay: 300 }"
class="flex-shrink-0"
@@ -53,6 +66,7 @@ import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import { app } from '@/scripts/app'
import { useSettingStore } from '@/stores/settingStore'
import { useRightSidebarTabStore } from '@/stores/workspace/rightSidebarTabStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import {
electronAPI,
@@ -63,6 +77,7 @@ import {
const workspaceState = useWorkspaceStore()
const settingStore = useSettingStore()
const rightSidebarTabStore = useRightSidebarTabStore()
const workflowTabsPosition = computed(() =>
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')

View File

@@ -83,7 +83,8 @@ export const CORE_SETTINGS: SettingParams[] = [
name: 'Sidebar location',
type: 'combo',
options: ['left', 'right'],
defaultValue: 'left'
defaultValue: 'left',
deprecated: true
},
{
id: 'Comfy.Sidebar.Size',

View File

@@ -0,0 +1,25 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useRightSidebarTabStore = defineStore('rightSidebarTab', () => {
const isVisible = ref(false)
const toggleVisibility = () => {
isVisible.value = !isVisible.value
}
const show = () => {
isVisible.value = true
}
const hide = () => {
isVisible.value = false
}
return {
isVisible,
toggleVisibility,
show,
hide
}
})

View File

@@ -317,6 +317,7 @@ const onGraphReady = () => {
z-index: 10;
grid-column: 3;
grid-row: 2;
display: flex;
}
.comfyui-body-bottom {