Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
d4ea383
feat(app-mode): layout grid primitive + system cells (Phase 1)
eliheuer Apr 16, 2026
96330d5
feat(bento): Phase 2a — per-input cells with uniform width
eliheuer Apr 14, 2026
0d31e3b
feat(bento): Phase 2b — per-output cells + design tokens
eliheuer Apr 14, 2026
43302f8
feat(app-mode): semi-customizable floating panels MVP (Phase 4)
eliheuer Apr 16, 2026
7da9659
style(app-mode): Phase 4 polish — typography, spacing, panel chrome
eliheuer Apr 16, 2026
451752b
refactor(app-mode): rename bento/ -> layout/
eliheuer Apr 16, 2026
1d16073
feat(app-mode): welcome screen — wordmark, copy, Run pill
eliheuer Apr 16, 2026
dde524f
feat(app-mode): unified chrome — shared sidebar + panel-matched cells
eliheuer Apr 16, 2026
8f8aa62
style(app-mode): flush left-dock + surface Run errors as toast
eliheuer Apr 16, 2026
a4addaa
i18n(app-mode): route output-thumb alt + block-drag aria-label throug…
eliheuer Apr 16, 2026
e28af8c
feat(app-builder): WYSIWYG floating panel shared with App Mode
eliheuer Apr 17, 2026
d9ae0dd
feat(app-mode): hoist Run cluster out of panel + mirror welcome copy
eliheuer Apr 17, 2026
c9b8e53
Merge branch 'main' into app-mode-semi-customizable-layout
DrJKL Apr 21, 2026
299292a
refactor(app-mode): address PR review feedback
eliheuer Apr 22, 2026
d436f54
fix(app-mode): second-round CodeRabbit feedback
eliheuer Apr 22, 2026
385b611
fix(app-mode): third-round CodeRabbit feedback
eliheuer Apr 22, 2026
9657403
Merge branch 'main' into app-mode-semi-customizable-layout
eliheuer Apr 22, 2026
3361a66
refactor(app-mode): unify App Mode and Builder chrome + panel
eliheuer Apr 23, 2026
3b70a54
refactor(app-mode): one grid system — fixed-gutter flex zones
eliheuer Apr 23, 2026
551a702
polish(app-builder): grid-aligned chrome bars + unified blue / orange…
eliheuer Apr 23, 2026
76dc717
polish(app-builder): highlight selectable widgets in builder:inputs
eliheuer Apr 23, 2026
550d855
polish(app-builder): output-selection parity + footer cleanup
eliheuer Apr 23, 2026
9adcea8
polish(app-mode): go/stop semantic cluster + run progress bar
eliheuer Apr 23, 2026
916ad31
refactor(app-mode): route go/stop colors through local CSS vars
eliheuer Apr 23, 2026
d621c7d
chore(app-mode): pre-squash CR-pattern cleanup
eliheuer Apr 23, 2026
69e1bff
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer Apr 23, 2026
82f1980
chore(app-mode): pre-squash cleanup from .agents/checks audit
eliheuer Apr 23, 2026
fd022de
refactor(app-mode): unify App Builder and App Mode into one WYSIWYG s…
eliheuer Apr 23, 2026
e8c7076
chore: drop unused type exports flagged by knip
eliheuer Apr 23, 2026
38b197c
polish(app-mode): pre-draft-flip panel UX pass
eliheuer Apr 23, 2026
adc1db4
chore(app-mode): pre-review code-quality cleanup pass
eliheuer Apr 24, 2026
fbddb28
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer Apr 24, 2026
d1f29f0
polish(app-mode): workspace pan/zoom + nav cluster + welcome touch-up
eliheuer Apr 24, 2026
32ab581
polish(app-mode): panel-block reorder — architecture + visuals
eliheuer Apr 24, 2026
583ba29
polish(app-mode): phase-3 drag demo + accent sweep
eliheuer Apr 26, 2026
0628acf
feat(app-mode): output preview as a movable window
eliheuer Apr 26, 2026
7c0d078
feat(app-mode): migrate run chrome into OutputWindow
eliheuer Apr 26, 2026
ccf166d
chore(app-mode): drop dead Interrupt/Progress cells
eliheuer Apr 26, 2026
4083442
feat(app-mode): multi-window output workspace (scaffolding)
eliheuer Apr 26, 2026
f067835
refactor(app-mode): drop the thumbnail history strip
eliheuer Apr 27, 2026
fc8e2f3
feat(app-mode): multi-window polish — asset resolution + close + clea…
eliheuer Apr 27, 2026
09d6ff6
polish(app-mode): UI pass — panel/window chrome, widget outlines, aut…
eliheuer Apr 27, 2026
8f3b9be
polish(app-mode): output overlay + widget chrome refinements
eliheuer Apr 27, 2026
d2e637b
polish(app-mode): floating-panel layout fixes + pre-review cleanup
eliheuer Apr 27, 2026
98f57a9
polish(app-mode): pan reliability + drag perf + dot-grid polish
eliheuer Apr 27, 2026
8c3fb37
polish(app-mode): address outstanding review nits
eliheuer Apr 27, 2026
a4128c8
docs(app-mode): defer agent design-system discoverability work
eliheuer Apr 27, 2026
ea0601a
chore(app-mode): comment trim pass for review (-343 lines)
eliheuer Apr 27, 2026
201fc1e
polish(app-mode): progress bar tracks sampler + window snap during drag
eliheuer Apr 27, 2026
bd4f9ef
chore(app-mode): trim small net-new files (-82 lines)
eliheuer Apr 27, 2026
17ecf38
refactor(app-mode): extract shared PanelHeader component
eliheuer Apr 27, 2026
895c0c1
refactor(app-mode): extract usePointerDrag base hook
eliheuer Apr 28, 2026
56bbd35
chore(app-mode): trim PR-added comments in modified files (-15 lines)
eliheuer Apr 28, 2026
d8355f4
chore(app-mode): drop remaining `!` Tailwind in chrome cells
eliheuer Apr 28, 2026
543f4b3
chore(app-mode): address CR review nits (batch 1/3)
eliheuer Apr 28, 2026
75b77b0
chore(app-mode): address CR review minors (batch 2/3)
eliheuer Apr 28, 2026
d676184
fix(app-mode): address CR review majors (batch 3/3)
eliheuer Apr 28, 2026
ed93de5
docs(app-mode): drop stale check count in discoverability note
eliheuer Apr 28, 2026
36c23a6
test(app-mode): fix LinearWelcome tests broken by run-pill addition
eliheuer Apr 28, 2026
ff60b57
polish(builder): step badges + Preview-step layout
eliheuer Apr 28, 2026
1289e61
refactor(app-mode): drop temp accent/active tokens for design-system
eliheuer Apr 28, 2026
d4dc570
refactor(builder): unify selection chrome, drop zoom-dependent sizing
eliheuer Apr 28, 2026
f6a29be
refactor(design-system): fold layout tokens into style.css
eliheuer Apr 28, 2026
c497c8c
refactor(design-system): consolidate App Mode tokens, drop README
eliheuer Apr 28, 2026
90f140b
chore(app-mode): trim noisy comments across new files
eliheuer Apr 28, 2026
563742f
feat(app-mode): resize OutputWindow, smooth fly-to, grid-edge spawn
eliheuer Apr 29, 2026
1d16598
feat(app-mode): align panel widgets + metadata-driven subtitle
eliheuer Apr 29, 2026
61ff478
refactor(app-mode): extract FLIP composable, persist panel layout
eliheuer Apr 29, 2026
d434a0d
chore(app-mode): comment sweep + LinearArrange refactor
eliheuer Apr 29, 2026
91bd183
refactor(app-mode): center welcome card; drop side-flip machinery
eliheuer Apr 29, 2026
b007a55
fix(app-mode): drop double focus-ring on widget pills
eliheuer Apr 29, 2026
676a443
Merge branch 'main' into app-mode-semi-customizable-layout
DrJKL Apr 29, 2026
13b3e09
feat(app-mode): no-zoom dashboard mode with adaptive bento layout
eliheuer Apr 30, 2026
6f91765
feat(app-mode): add slicing-tree bento candidate
eliheuer Apr 30, 2026
2a21d12
fix(app-mode): snap dashboard tiles to chrome cell grid
eliheuer Apr 30, 2026
c274133
feat(app-mode): fill canvas via slicing-tree + stretch + cover-fit
eliheuer Apr 30, 2026
0d18fe0
feat(app-mode): asymmetric bento stretch — newest tile uncropped
eliheuer May 1, 2026
8f00acb
feat(app-mode): cap dashboard at 9 tiles, full slicing-tree always
eliheuer May 1, 2026
5edcdf8
fix(app-mode): drop the 8px body padding around output images
eliheuer May 1, 2026
274d32c
fix(app-mode): N=1 stays uncropped, anchored opposite the panel
eliheuer May 1, 2026
152653a
refactor(app-mode): rebuild dashboard layout for visual fidelity
eliheuer May 1, 2026
1f0cfea
feat(app-mode): bento templates fill avail rect, hide header on small…
eliheuer May 1, 2026
de52814
refactor(app-mode): clean up output header chrome + fix panel-drag fr…
eliheuer May 1, 2026
4754980
style(app-mode): match input-cell label/subtitle type, breathe out te…
eliheuer May 1, 2026
681a959
feat(app-mode): theme toggle, zoom-mode parity, gradient skeletons
eliheuer May 2, 2026
989754e
Merge remote-tracking branch 'upstream/main' into app-mode-semi-custo…
eliheuer May 6, 2026
4fb60b8
refactor(app-mode): use palette tokens instead of local --app-mode-* …
eliheuer May 6, 2026
2746856
refactor(app-mode): tighten design-system conformance
eliheuer May 6, 2026
9c171c1
refactor(app-mode): conformance pass per AGENTS.md
eliheuer May 6, 2026
055e46e
refactor(app-mode): minor cleanups from deep audit
eliheuer May 7, 2026
2640d09
lint(autofix): tailwind class order in files using foreground tokens
eliheuer May 7, 2026
c6cf567
test(app-mode): include layout.panelRows in linearData expectations
eliheuer May 7, 2026
fd75632
test(app-mode): unit coverage for new store math
eliheuer May 7, 2026
1acdb42
refactor(app-mode): scope foreground tokens to App Mode via arbitrary…
eliheuer May 7, 2026
a4d4145
Revert "lint(autofix): tailwind class order in files using foreground…
eliheuer May 7, 2026
1a7300b
lint(autofix): canonical class names in pre-existing files
eliheuer May 7, 2026
7e5a95a
fix(app-mode): evict by createdSeq, not mutable zIndex
eliheuer May 7, 2026
6b9dfe8
fix(test): pass ComfyPage to reSaveAs instead of dereferencing Locator
eliheuer May 7, 2026
f357c57
fix(app-mode): spread graphNodes shallowRef on every assignment
eliheuer May 7, 2026
c668618
fix(app-mode): disable manual drag/resize on output windows in noZoom…
eliheuer May 7, 2026
4c140cb
fix(app-mode): don't silently rerun current graph when output has no …
eliheuer May 7, 2026
1a1025b
fix(app-mode): scope wheel-zoom to the workspace, leave panel scroll …
eliheuer May 7, 2026
c48d731
fix(app-mode): keep full-viewport backdrop hit-testable for pan/zoom
eliheuer May 7, 2026
5a31405
fix(app-mode): address 5 minor CodeRabbit findings
eliheuer May 7, 2026
2f7e90a
fix(app-mode): address 5 load-bearing CodeRabbit findings (round 2)
eliheuer May 8, 2026
7a5e375
docs(adr): add 0009 App Mode + Builder unification; format pass
eliheuer May 9, 2026
c997195
fix(app-mode): address round-3 CodeRabbit findings (5 follow-ons + 4 …
eliheuer May 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(app-mode): extract FLIP composable, persist panel layout
PanelBlockList:
- Drop :input(:not(textarea)) tautology; <textarea> isn't an <input>.
- Stabilize row v-for key (rowIdx) so in-row reorders don't remount.
- Replace inline FLIP block with useFlipReorder() — Web Animations API,
  no double-RAF, no manual transition swap.
- Standard scale: gap-[10px] → gap-2.5, outline-offset-[6px] → -1.5.

useFlipReorder:
- New composable. Captures rects in onBeforeUpdate, plays diffs back
  via el.animate() in onUpdated. Sub-pixel deltas skipped to avoid
  jiggling on unrelated reactive updates. skipKey opt-out for the
  dragging element so its lift treatment doesn't compete with the
  slide on its siblings.

panelPresetClasses:
- Trim 60 → 28 lines. Constant names speak for themselves; one short
  doc note kept for the non-obvious sidebar-offset gotcha.

Panel layout persistence:
- New fields under extra.linearData.layout: panelPreset, panelCollapsed,
  panelWidthCells, panelRows. Loaded in loadSelections; written from
  the existing builder-mode watch (block reorder is builder-only).
- Schema + LinearLayoutConfig type extended.
- Drop the "in-memory only" comment from panelTypes.ts.
  • Loading branch information
eliheuer committed Apr 29, 2026
commit 61ff4781c7fe4dc5ab2c394c85bf07d265901890
61 changes: 10 additions & 51 deletions src/components/appMode/layout/panels/PanelBlockList.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { cn } from '@comfyorg/tailwind-utils'
import { computed, onBeforeUpdate, onUpdated, useTemplateRef } from 'vue'
import { computed, useTemplateRef } from 'vue'

import InputCell from '../cells/InputCell.vue'
import type { InputCellEntry, InputCellVariant } from '../cells/InputCell.vue'
import type { BlockPos, BlockRow, DropTarget } from './panelTypes'
import { applyMove } from './useAppPanelLayout'
import { useBlockDrag } from './useBlockDrag'
import { useFlipReorder } from './useFlipReorder'

const { rows, variant = 'app-mode' } = defineProps<{
rows: BlockRow[]
Expand Down Expand Up @@ -58,58 +59,16 @@ function beginDrag(blockId: string, event: PointerEvent) {
if (pos) startDrag(pos, event)
}

// FLIP reorder animation. Re-samples rects on each reactive update
// (drag-preview reshuffles + committed reorders).
const FLIP_DURATION_MS = 200
const prevRects = new Map<string, DOMRect>()

onBeforeUpdate(() => {
prevRects.clear()
const els = listEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
if (!els) return
for (const el of els) {
const key = el.dataset.flipKey
if (key) prevRects.set(key, el.getBoundingClientRect())
}
})

onUpdated(() => {
const els = listEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
if (!els) return
const draggingId = draggingBlockId.value
for (const el of els) {
const key = el.dataset.flipKey
if (!key || key === draggingId) continue
const prev = prevRects.get(key)
if (!prev) continue
const next = el.getBoundingClientRect()
const dx = prev.left - next.left
const dy = prev.top - next.top
// Skip sub-pixel deltas — would animate a jiggle every keystroke.
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue
// Double-RAF: write the inverse transform, then re-enable transition
// on the next frame so the browser doesn't batch and skip.
el.style.transform = `translate(${dx}px, ${dy}px)`
el.style.transition = 'none'
requestAnimationFrame(() => {
requestAnimationFrame(() => {
el.style.transition = `transform ${FLIP_DURATION_MS}ms ease`
el.style.transform = ''
})
})
}
})
// Skip the dragging block — its lift treatment shouldn't compete
// with the slide animation on its siblings.
useFlipReorder(listEl, { skipKey: () => draggingBlockId.value })
</script>

<template>
<ul
ref="listEl"
class="m-0 flex list-none flex-col gap-[10px] p-0"
role="list"
>
<ul ref="listEl" class="m-0 flex list-none flex-col gap-2.5 p-0" role="list">
<li
v-for="(row, rowIdx) in displayRows"
:key="`row-${rowIdx}-${row[0]?.id}`"
:key="rowIdx"
class="flex min-w-0 flex-row items-stretch gap-4"
>
<div
Expand All @@ -122,11 +81,11 @@ onUpdated(() => {
'duration-layout transition-[box-shadow,transform] ease-layout',
variant === 'builder' && [
'cursor-grab touch-none active:cursor-grabbing',
'hover:outline-2 hover:outline-offset-[6px] hover:outline-warning-background hover:outline-dashed'
'hover:outline-offset-1.5 hover:outline-2 hover:outline-warning-background hover:outline-dashed'
],
block.id === draggingBlockId && [
'z-20 scale-[1.02] shadow-2xl',
'outline-2 outline-offset-[6px] outline-warning-background outline-dashed'
'outline-offset-1.5 outline-2 outline-warning-background outline-dashed'
]
)
"
Expand Down Expand Up @@ -168,7 +127,7 @@ onUpdated(() => {
<!-- :deep() into NodeWidgets internals; documented exception in
docs/guidance/vue-components.md §Styling. -->
<style scoped>
.panel-block__input :deep(input:not(textarea)) {
.panel-block__input :deep(input) {
text-align: center;
}

Expand Down
43 changes: 5 additions & 38 deletions src/components/appMode/layout/panels/panelPresetClasses.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,19 @@
/**
* Shared Tailwind class strings for each FloatingPanel preset
* position. Consumed by FloatingPanel (the live panel) and
* PanelDragPreview (the blue drop-target outline) so the two always
* land at the same coordinates.
* Positioning classes per FloatingPanel preset. Consumed by
* FloatingPanel and PanelDragPreview so the live panel and drop-
* target outline land at identical coordinates.
*
* The anchor + height chunks are named constants so each preset's
* geometry reads as a short combination instead of a wall of
* repeated `calc(...)` expressions. Keep the Tailwind arbitrary-
* value strings on a single line — the JIT scanner needs to see
* the full `h-[calc(...)]` / `top-[calc(...)]` token unbroken.
*
* Left anchors use `--spacing-layout-outer` only — no sidebar offset.
* Both consumers position FloatingPanel inside a container whose
* origin already starts after the Comfy sidebar (LayoutView is a
* flex sibling of the sidebar; BuilderPanel's wrapper applies its
* own `left-(--sidebar-width,0px)`), so adding the sidebar width
* here would double-count and leave a visible gap. The outer
* padding matches the chrome-cell column on the same side so the
* panel reads as part of the same vertical rail.
* Left/right offsets assume the parent container already excludes
* the Comfy sidebar — don't add a sidebar width here.
*/
import type { PanelPreset } from './panelTypes'

// Top / bottom anchors (outer padding + 1 cell row + gutter below
// the chrome rail). Named for readability in the preset map.
const TOP_BELOW_CHROME =
'top-[calc(var(--spacing-layout-outer)+var(--spacing-layout-cell)+var(--spacing-layout-gutter))]'
const BOTTOM_ABOVE_CHROME =
'bottom-[calc(var(--spacing-layout-outer)+var(--spacing-layout-cell)+var(--spacing-layout-gutter))]'

// Side anchors. Both edges use `--spacing-layout-outer` so the panel
// aligns with the chrome-cell column on its side (top-left chrome
// zone is at `left-(--spacing-layout-outer)`, top-right at
// `right-(--spacing-layout-outer)` — the panel should hug the same
// rail). The panel's ancestor already excludes the Comfy sidebar
// (LayoutView via flex sibling, BuilderPanel via its wrapper's
// `left-(--sidebar-width,0px)`), so no extra sidebar offset here.
const RIGHT_EDGE = 'right-(--spacing-layout-outer)'
const LEFT_EDGE = 'left-(--spacing-layout-outer)'

// Vertical size. Dock presets pin BOTH top and bottom anchors so the
// panel always spans the full slot between the chrome rails — the
// body's `overflow-y-auto` handles overflow when the widget list is
// taller than the slot, and short widget lists get the breathing
// room of the available empty space rather than leaving it unused.
// Float presets are content-driven from their anchored corner
// outward, capped at the same full-slot height so the inner edge
// expands to show every widget that fits and only scrolls once the
// panel actually reaches the opposite chrome rail.
const SLOT_MAX_H =
'max-h-[calc(100%-var(--spacing-layout-outer)*2-var(--spacing-layout-cell)*2-var(--spacing-layout-gutter)*2)]'
Comment on lines +11 to +18
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/appMode/layout/panels/panelPresetClasses.ts | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1590


🌐 Web query:

Tailwind CSS arbitrary values spaces underscores calc function

💡 Result:

In Tailwind CSS, arbitrary values use square bracket notation like w-[200px] to apply any CSS value directly.[1] Spaces are not allowed in class names because browsers split classes on spaces, so they must be replaced with underscores () which Tailwind automatically converts to spaces at build time.[1][2] For example, grid-cols-[1fr_500px_2fr] becomes grid-cols: 1fr 500px 2fr in CSS.[1] This applies specifically to the calc function in arbitrary values. Spaces inside calc must be replaced with underscores: w-[calc(100%-2rem)] generates width: calc(100% - 2rem);[3][1][4][5] Spaces like w-[calc(100% - 2rem)] break because the browser parses it as multiple classes.[6][7] Underscores are converted to spaces unless in contexts like URLs (where they are preserved), the first argument of var or theme, or explicitly escaped with backslash (\) to keep the underscore.[1][8][2] For calc, use underscores for operator spacing: h-[calc(100vh_-64px)].[3] This behavior is unchanged in Tailwind CSS v4 (latest release v4.2.4 as of 2026-04).[9] Examples: - Valid: w-[calc(100%-20px)], p-[calc(3rem*_2)] - Invalid (use ): w-[calc(100% - 20px)] In JS/JSX, escape backslashes with String.rawhello\\_world to preserve \ if needed.[8]

Citations:


🌐 Web query:

CSS calc() operator whitespace requirements specification

💡 Result:

The CSS calc function requires whitespace on both sides of the + (addition) and - (subtraction) operators due to the formal grammar defined in the CSS Values and Units specification. This is explicitly stated in section 8.1.1 (Syntax) of CSS Values and Units Module Level 3 (and carried forward in Levels 4 and 5): "In addition, white space is required on both sides of the + and - operators. (The * and / operators can be used without white space around them.)" [1][2]. The grammar productions are: = calc( ) = [ [ '+' | '-' ] ]* = [ [ '' | '/' ] ] = | | | ( ) [1][2][3]. This requirement prevents parsing ambiguities. Without spaces, expressions like calc(50% -8px) are parsed as a percentage followed by a negative length (-8px), which is invalid, rather than subtraction [4][5][6][3]. Similarly, calc(8px + -50%) would be a length followed by a unary minus on the percentage [4][6]. The hyphen (-) can appear in CSS identifiers/dimensions, so spaces distinguish the binary operator [7][6]. In contrast, * (multiplication) and / (division) do not require whitespace, though it is recommended for consistency [4][1][2][3]. This rule remains unchanged in the latest specifications (Levels 4 and 5 as of 2024/2025 drafts) [8][9]. MDN confirms: "The + and - operators must be surrounded by whitespace" [4][10][3].

Citations:


calc() expressions are missing required whitespace around + and - operators.

When an arbitrary value needs to contain a space, use an underscore (_) instead and Tailwind will automatically convert it to a space at build-time. The CSS spec (CSS Values and Units Module) explicitly requires whitespace on both sides of + and - in calc() — omitting it produces spec-invalid CSS that creates parsing ambiguity. The correct pattern is w-[calc(100%_-_2rem)] for proper parsing.

SLOT_MAX_H is especially at risk: calc(100%-var(...)…) can be misread as 100% followed by a unary-negated custom property, which is syntactically invalid.

🛠️ Proposed fix — use `_` as space placeholder
-const TOP_BELOW_CHROME =
-  'top-[calc(var(--spacing-layout-outer)+var(--spacing-layout-cell)+var(--spacing-layout-gutter))]'
-const BOTTOM_ABOVE_CHROME =
-  'bottom-[calc(var(--spacing-layout-outer)+var(--spacing-layout-cell)+var(--spacing-layout-gutter))]'
+const TOP_BELOW_CHROME =
+  'top-[calc(var(--spacing-layout-outer)_+_var(--spacing-layout-cell)_+_var(--spacing-layout-gutter))]'
+const BOTTOM_ABOVE_CHROME =
+  'bottom-[calc(var(--spacing-layout-outer)_+_var(--spacing-layout-cell)_+_var(--spacing-layout-gutter))]'
-const SLOT_MAX_H =
-  'max-h-[calc(100%-var(--spacing-layout-outer)*2-var(--spacing-layout-cell)*2-var(--spacing-layout-gutter)*2)]'
+const SLOT_MAX_H =
+  'max-h-[calc(100%_-_var(--spacing-layout-outer)*2_-_var(--spacing-layout-cell)*2_-_var(--spacing-layout-gutter)*2)]'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/appMode/layout/panels/panelPresetClasses.ts` around lines 11 -
18, The Tailwind arbitrary calc() values in TOP_BELOW_CHROME,
BOTTOM_ABOVE_CHROME and SLOT_MAX_H are missing required whitespace around + and
-; update those string constants to use underscores (_) as space placeholders
inside the calc expressions (e.g. use _+_ and _-_ between terms) so Tailwind
emits valid CSS, and also fix RIGHT_EDGE and LEFT_EDGE to use the correct
arbitrary var syntax (use a bracketed arbitrary value for
var(--spacing-layout-outer) instead of the current parenthesis form); modify the
constants TOP_BELOW_CHROME, BOTTOM_ABOVE_CHROME, SLOT_MAX_H, RIGHT_EDGE and
LEFT_EDGE accordingly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred — out of scope. The hex/rgb values in panelPresetClasses are deliberate visual identity (preset accents), not theme tokens. Documented in the LatentPreview comment block as the project's pattern for fixed-identity placeholder visuals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description images show:

  • Image 1: App workspace with two floating result cards, a right-side prompt panel, and a centered “Output” progress window (step 13/30, 32s). Filename cards have actions; the Run controls are top-right.
  • Image 2: A masonry gallery of cyberpunk-eye results with the same right-side prompt/size panel; “Number of runs” and Run in header.
  • Image 3: Bunny calligraphy set with an in-progress overlay bar (step 23/30) and a large detail card.
  • Image 4: Tall 512×768 gallery with a small floating progress dialog; right panel shows a long prompt.
  • Image 5: 1024×1024 variation grid with a large selected image on the left and an Input panel on the right.

@eliheuer Thanks — acknowledged on the fixed-identity color accents; I won’t push theme tokens there.

Separate from colors, my note on src/components/appMode/layout/panels/panelPresetClasses.ts (Line 11–18) was about calc() spacing. To avoid calc parsing issues and keep Tailwind arbitrary values valid, we should add underscores as space placeholders around + and - inside the calc() expressions. RIGHT_EDGE/LEFT_EDGE using the v4 var shorthand (right-(--spacing-layout-outer)/left-(--…)) are fine as-is.

Here’s the minimal, targeted tweak:

// panelPresetClasses.ts
const TOP_BELOW_CHROME =
  'top-[calc(var(--spacing-layout-outer)_+_var(--spacing-layout-cell)_+_var(--spacing-layout-gutter))]'

const BOTTOM_ABOVE_CHROME =
  'bottom-[calc(var(--spacing-layout-outer)_+_var(--spacing-layout-cell)_+_var(--spacing-layout-gutter))]'

const RIGHT_EDGE = 'right-(--spacing-layout-outer)'
const LEFT_EDGE = 'left-(--spacing-layout-outer)'

const SLOT_MAX_H =
  'max-h-[calc(100%_-_var(--spacing-layout-outer)*2_-_var(--spacing-layout-cell)*2_-_var(--spacing-layout-gutter)*2)]'

If you’re okay with this tiny calc fix, I’ll consider the thread closed with colors deferred.


✏️ Learnings added
Learnt from: eliheuer
Repo: Comfy-Org/ComfyUI_frontend PR: 11317
File: src/components/appMode/layout/panels/panelPresetClasses.ts:11-18
Timestamp: 2026-05-08T23:32:44.886Z
Learning: ComfyUI_frontend: In panelPresetClasses.ts and similar UI “preset accent” contexts, hard-coded hex/rgb values are an intentional visual identity and are not to be converted to theme tokens. Treat such colors as out-of-scope for theming refactors unless the author requests otherwise.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7906
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:545-552
Timestamp: 2026-01-12T17:39:27.738Z
Learning: In Vue/TypeScript files (src/**/*.{ts,tsx,vue}), prefer if/else statements over ternary operators when performing side effects or actions (e.g., mutating state, calling methods with side effects). Ternaries should be reserved for computing and returning values.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8248
File: src/platform/assets/schemas/assetSchema.ts:109-118
Timestamp: 2026-01-22T23:44:59.888Z
Learning: Do not export declarations (e.g., schemas, constants, types) unless they are actually used elsewhere in the codebase. Keep the public API surface minimal by exporting only items with concrete usage. This reduces coupling and keeps imports explicit. In src/platform/assets/schemas/assetSchema.ts, ensure only necessary exports are exposed; remove unused exports or switch to non-exported/internal where appropriate.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8722
File: src/composables/maskeditor/useBrushDrawing.ts:5-5
Timestamp: 2026-02-08T03:19:12.009Z
Learning: Treat unresolved import warnings caused by pnpm workspace catalog mappings (pnpm-workspace.yaml) and unplugin-typegpu in vite.config.mts as false positives. Do not flag these in PR reviews; they are expected in sandboxes without node_modules and are not indicative of actual missing dependencies in the TS codebase.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/load3d.ts:427-430
Timestamp: 2026-02-19T02:06:23.468Z
Learning: In TypeScript, you can use typeof SomeClass in type annotation positions (e.g., param: typeof LGraphNode) even when SomeClass is imported with import type. This is a type query that only exists at compile time and is erased in runtime, so it is safe to combine with type-only imports. Apply this pattern to TS files broadly when you need a type that references the shape of a class or constructor function without importing the value at runtime.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/uploadAudio.ts:91-94
Timestamp: 2026-02-19T02:06:38.395Z
Learning: In TypeScript files, you can use a type annotation like 'nodeType: typeof MyClass' even if MyClass is imported via 'import type'. Both the type-only import and 'typeof' operate at the type level and are erased at compile time. This pattern is commonly used for constructor types (e.g., 'nodeType: typeof LGraphNode'). Apply this pattern across TypeScript files in the repository (src/**/*.ts) as appropriate, ensuring the imported symbol is a type-only import when possible for clarity and to avoid runtime imports.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8992
File: src/lib/litegraph/src/widgets/GradientSliderWidget.ts:18-18
Timestamp: 2026-02-20T21:08:19.814Z
Learning: When drawing with CanvasRenderingContext2D in TypeScript/JavaScript, wrap the drawing logic with ctx.save() at the start and ctx.restore() at the end to preserve and restore the canvas state. Do not manually destructure and restore individual properties (e.g., fillStyle, strokeStyle); rely on save/restore to manage state changes in a scoped manner. This should be applied to all TS files that perform canvas drawing.

Learnt from: dante01yoon
Repo: Comfy-Org/ComfyUI_frontend PR: 9075
File: src/scripts/api.featureFlags.test.ts:237-268
Timestamp: 2026-02-22T04:27:33.379Z
Learning: In Vite/Vitest, import.meta.env.DEV is true for any mode that is not 'production' (i.e., DEV is the opposite of PROD, and can be true in 'test', 'development', etc.). Do not assume DEV implies only 'development' mode. When reviewing code and tests, treat DEV as a non-production flag and verify environment-specific logic accordingly. Reference: https://vite.dev/guide/env-and-mode#modes

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 9427
File: src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuFilter.vue:33-33
Timestamp: 2026-03-06T00:53:28.835Z
Learning: When reviewing code, note that the enforce-canonical-classes (better-tailwindcss) rule may auto-fix Tailwind v3 !class-name syntax by converting it to v4 class-name! syntax. Do not treat these auto-fixed class-name! instances as newly introduced issues; the perceived change is in syntax placement, not in usage or intent. This guidance applies across all .vue and .ts files in the repository.

Learnt from: sonnybox
Repo: Comfy-Org/ComfyUI_frontend PR: 9446
File: src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue:45-45
Timestamp: 2026-03-06T01:55:00.013Z
Learning: Treat wrap-break-word as a valid Tailwind CSS utility for overflow-wrap: break-word in Tailwind v4+ projects. Do not flag this class as invalid in any Vue (.vue) or TypeScript (.ts/.tsx) files within the repository (e.g., Comfy-Org/ComfyUI_frontend) or other Tailwind v4+ projects. When reviewing, verify that the class is used to enable word breaking in long text content and reference the Tailwind docs: https://tailwindcss.com/docs/overflow-wrap.

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 11531
File: src/composables/maskeditor/brushDrawingUtils.ts:14-14
Timestamp: 2026-04-22T04:06:01.353Z
Learning: In the Comfy-Org/ComfyUI_frontend codebase, `knip` is used to detect unused exports. When reviewing TypeScript/TSX code, avoid recommending or introducing exported types/functions/constants that have no concrete external consumers (e.g., they’re only used within the same module). Only recommend exporting when there is an actual external usage outside the module. If something might be needed externally later, keep it non-exported for now and defer exporting until an external consumer is added.

Learnt from: eliheuer
Repo: Comfy-Org/ComfyUI_frontend PR: 11317
File: src/components/appMode/layout/panels/useAppPanelLayout.ts:0-0
Timestamp: 2026-05-07T11:21:56.210Z
Learning: In the Vue 3/TypeScript frontend (ComfyUI_frontend), when LiteGraph mutates/reuses graph node arrays in place, Vue reactivity may not trigger if you only mirror/mutate the existing array. When updating state backed by a shallowRef (e.g., for derived panel/layout data), assign a new array reference when mirroring nodes—e.g., `graphNodes.value = [...(app.rootGraph?.nodes ?? [])]`—so recomputed values like `inputEntries`/`panelRows` rerun on reconfigure.


Expand Down
1 change: 0 additions & 1 deletion src/components/appMode/layout/panels/panelTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/**
* Panel + block config types for the semi-customizable floating panel.
* In-memory only for now; persistence is a follow-up.
*/

export type PanelPreset =
Expand Down
59 changes: 59 additions & 0 deletions src/components/appMode/layout/panels/useFlipReorder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { onBeforeUpdate, onUpdated } from 'vue'
import type { Ref } from 'vue'

interface FlipReorderOptions {
/** Animation duration in ms. Default 200. */
durationMs?: number
/** Returns the flip-key of an element to skip — used to keep a
* drag/lift treatment in place while siblings animate. */
skipKey?: () => string | null
}

/**
* FLIP-animates `[data-flip-key]` descendants of `containerEl` whose
* positions changed between Vue patches.
*
* Captures rects in `onBeforeUpdate`, then in `onUpdated` plays the
* difference back via the Web Animations API. Sub-pixel deltas are
* skipped so unrelated reactive updates don't jiggle the layout.
*/
export function useFlipReorder(
containerEl: Ref<HTMLElement | null>,
options: FlipReorderOptions = {}
): void {
const { durationMs = 200, skipKey } = options
const prevRects = new Map<string, DOMRect>()

onBeforeUpdate(() => {
prevRects.clear()
const els =
containerEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
if (!els) return
for (const el of els) {
const key = el.dataset.flipKey
if (key) prevRects.set(key, el.getBoundingClientRect())
}
})

onUpdated(() => {
const skip = skipKey?.() ?? null
const els =
containerEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
if (!els) return
for (const el of els) {
const key = el.dataset.flipKey
if (!key || key === skip) continue
const prev = prevRects.get(key)
if (!prev) continue
const next = el.getBoundingClientRect()
const dx = prev.left - next.left
const dy = prev.top - next.top
// Sub-pixel deltas would jiggle on every reactive update.
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue
el.animate(
[{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'none' }],
{ duration: durationMs, easing: 'ease' }
)
}
Comment on lines +33 to +52
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Honor reduced-motion when replaying FLIP animations.

This path animates every reorder via WAAPI, so users who have prefers-reduced-motion: reduce still get list-slide motion on every patch. A small guard here keeps the reorder behavior while avoiding forced animation for motion-sensitive users.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/appMode/layout/panels/useFlipReorder.ts` around lines 33 - 52,
The FLIP replay in onUpdated ignores users' reduced-motion preference; add a
guard using window.matchMedia('(prefers-reduced-motion: reduce)').matches (or
equivalent) and skip WAAPI animations when it returns true. In practice, check
this once at the start of the onUpdated callback (or before the animation loop)
and if reduced-motion is enabled simply return or continue so the loop does not
call el.animate; reference onUpdated, skipKey, containerEl, prevRects and
durationMs to find where to insert the check.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred — prefers-reduced-motion is a real concern but FLIP reordering is fundamental to the panel UX. The right fix is to short-circuit FLIP into an instant snap when reduced-motion is set, which needs a small composable refactor. Worth a follow-up but outside this PR's scope.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description images show:

  • Image 1: App view with two image cards and a floating “Output” window showing step 13/30 progress; a right panel edits prompt/size/filename.
  • Image 2: A dense grid of eye images; right panel “green-eyes-001” with prompt and 512×512 controls.
  • Image 3: “anima-bunny-001” session; multiple bunny outputs with a small floating progress bar; right prompt panel.
  • Image 4: Large gallery of cyberpunk eyes; floating Output shows step 20/20; right panel uses a tall 512×768 size.
  • Image 5: “bunny-anima-001” with 1024×1024 main image, five variants, and an “Input” panel on the right.

@eliheuer Got it—agree on deferring. I’ll queue a follow-up to short-circuit FLIP when reduced motion is enabled so reorders “snap” without animation.

If helpful later, here’s the minimal composable change that keeps behavior identical but skips WAAPI when users prefer reduced motion. It doesn’t alter panel UX; it only gates the animate() call.

// src/components/appMode/layout/panels/useFlipReorder.ts
function prefersReducedMotion(): boolean {
  try {
    return typeof window !== 'undefined' &&
      typeof window.matchMedia === 'function' &&
      window.matchMedia('(prefers-reduced-motion: reduce)').matches
  } catch {
    return false
  }
}

onBeforeUpdate(() => {
  prevRects.clear()
  if (prefersReducedMotion()) return
  const els = containerEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
  if (!els) return
  for (const el of els) {
    const key = el.dataset.flipKey
    if (key) prevRects.set(key, el.getBoundingClientRect())
  }
})

onUpdated(() => {
  if (prefersReducedMotion()) return // instant snap: no WAAPI
  const skip = skipKey?.() ?? null
  const els = containerEl.value?.querySelectorAll<HTMLElement>('[data-flip-key]')
  if (!els) return
  for (const el of els) {
    const key = el.dataset.flipKey
    if (!key || key === skip) continue
    const prev = prevRects.get(key)
    if (!prev) continue
    const next = el.getBoundingClientRect()
    const dx = prev.left - next.left
    const dy = prev.top - next.top
    if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue
    el.animate(
      [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: 'none' }],
      { duration: durationMs, easing: 'ease' }
    )
  }
})

Want me to open a follow-up GitHub issue now with:

  • Title: Respect prefers-reduced-motion in useFlipReorder (instant snap instead of WAAPI)
  • Summary/scope: Gate FLIP replay behind prefers-reduced-motion; keep DOM reorders, skip animations.
  • Acceptance criteria:
    • With matchMedia('(prefers-reduced-motion: reduce)') true, no calls to Element.animate during reorder; items snap instantly.
    • With it false, current animations remain unchanged.
    • Unit test stubs matchMedia and asserts animate not called when reduced.
      If yes, I’ll file it and backlink to this PR and your comment, assigning it to you unless you prefer someone else.

✏️ Learnings added
Learnt from: eliheuer
Repo: Comfy-Org/ComfyUI_frontend PR: 11317
File: src/components/appMode/layout/panels/useFlipReorder.ts:33-52
Timestamp: 2026-05-09T03:26:06.074Z
Learning: In Comfy-Org/ComfyUI_frontend, FLIP reorder animations (useFlipReorder.ts) are core to panel UX. For accessibility, the correct approach is to honor prefers-reduced-motion by skipping WAAPI (instant snap) rather than removing reordering. Treat as a follow-up item when out of scope for a PR.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7169
File: src/platform/remote/comfyui/jobs/jobTypes.ts:1-107
Timestamp: 2025-12-09T03:39:54.501Z
Learning: In the ComfyUI_frontend project, Zod is on v3.x. Do not suggest Zod v4 standalone validators (z.uuid, z.ulid, z.cuid2, z.nanoid) until an upgrade to Zod 4 is performed. When reviewing TypeScript files (e.g., src/platform/remote/comfyui/jobs/jobTypes.ts) validate against Zod 3 capabilities and avoid introducing v4-specific features; flag any proposal to upgrade or incorporate v4-only validators and propose staying with compatible 3.x patterns.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7358
File: src/components/dialog/content/signin/SignUpForm.vue:45-54
Timestamp: 2025-12-11T12:25:15.470Z
Learning: This repository uses CI automation to format code (pnpm format). Do not include manual formatting suggestions in code reviews for Comfy-Org/ComfyUI_frontend. If formatting issues are detected, rely on the CI formatter or re-run pnpm format. Focus reviews on correctness, readability, performance, accessibility, and maintainability rather than style formatting.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 7416
File: src/stores/imagePreviewStore.ts:5-7
Timestamp: 2025-12-13T11:03:11.264Z
Learning: In the ComfyUI_frontend repository, lint rules require keeping 'import type' statements separate from non-type imports, even if importing from the same module. Do not suggest consolidating them into a single import statement. Ensure type imports remain on their own line (import type { ... } from 'module') and regular imports stay on separate lines.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7537
File: src/components/ui/button/Button.stories.ts:45-55
Timestamp: 2025-12-17T00:40:09.635Z
Learning: Prefer pure function declarations over function expressions (e.g., use function foo() { ... } instead of const foo = () => { ... }) for pure functions in the repository. Function declarations are more functional-leaning, offer better hoisting clarity, and can improve readability and tooling consistency. Apply this guideline across TypeScript files in Comfy-Org/ComfyUI_frontend, including story and UI component code, except where a function expression is semantically required (e.g., callbacks, higher-order functions with closures).

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 7805
File: src/composables/useCoreCommands.ts:439-439
Timestamp: 2025-12-30T22:22:33.836Z
Learning: When accessing reactive properties from Pinia stores in TypeScript files, avoid using .value on direct property access (e.g., useStore().isOverlayExpanded). Pinia auto-wraps refs when accessed directly, returning the primitive value. The .value accessor is only needed when destructuring store properties or when using storeToRefs().

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7906
File: src/components/sidebar/tabs/AssetsSidebarTab.vue:545-552
Timestamp: 2026-01-12T17:39:27.738Z
Learning: In Vue/TypeScript files (src/**/*.{ts,tsx,vue}), prefer if/else statements over ternary operators when performing side effects or actions (e.g., mutating state, calling methods with side effects). Ternaries should be reserved for computing and returning values.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8248
File: src/platform/assets/schemas/assetSchema.ts:109-118
Timestamp: 2026-01-22T23:44:59.888Z
Learning: Do not export declarations (e.g., schemas, constants, types) unless they are actually used elsewhere in the codebase. Keep the public API surface minimal by exporting only items with concrete usage. This reduces coupling and keeps imports explicit. In src/platform/assets/schemas/assetSchema.ts, ensure only necessary exports are exposed; remove unused exports or switch to non-exported/internal where appropriate.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8722
File: src/composables/maskeditor/useBrushDrawing.ts:5-5
Timestamp: 2026-02-08T03:19:12.009Z
Learning: Treat unresolved import warnings caused by pnpm workspace catalog mappings (pnpm-workspace.yaml) and unplugin-typegpu in vite.config.mts as false positives. Do not flag these in PR reviews; they are expected in sandboxes without node_modules and are not indicative of actual missing dependencies in the TS codebase.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/load3d.ts:427-430
Timestamp: 2026-02-19T02:06:23.468Z
Learning: In TypeScript, you can use typeof SomeClass in type annotation positions (e.g., param: typeof LGraphNode) even when SomeClass is imported with import type. This is a type query that only exists at compile time and is erased in runtime, so it is safe to combine with type-only imports. Apply this pattern to TS files broadly when you need a type that references the shape of a class or constructor function without importing the value at runtime.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8966
File: src/extensions/core/uploadAudio.ts:91-94
Timestamp: 2026-02-19T02:06:38.395Z
Learning: In TypeScript files, you can use a type annotation like 'nodeType: typeof MyClass' even if MyClass is imported via 'import type'. Both the type-only import and 'typeof' operate at the type level and are erased at compile time. This pattern is commonly used for constructor types (e.g., 'nodeType: typeof LGraphNode'). Apply this pattern across TypeScript files in the repository (src/**/*.ts) as appropriate, ensuring the imported symbol is a type-only import when possible for clarity and to avoid runtime imports.

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8992
File: src/lib/litegraph/src/widgets/GradientSliderWidget.ts:18-18
Timestamp: 2026-02-20T21:08:19.814Z
Learning: When drawing with CanvasRenderingContext2D in TypeScript/JavaScript, wrap the drawing logic with ctx.save() at the start and ctx.restore() at the end to preserve and restore the canvas state. Do not manually destructure and restore individual properties (e.g., fillStyle, strokeStyle); rely on save/restore to manage state changes in a scoped manner. This should be applied to all TS files that perform canvas drawing.

Learnt from: dante01yoon
Repo: Comfy-Org/ComfyUI_frontend PR: 9075
File: src/scripts/api.featureFlags.test.ts:237-268
Timestamp: 2026-02-22T04:27:33.379Z
Learning: In Vite/Vitest, import.meta.env.DEV is true for any mode that is not 'production' (i.e., DEV is the opposite of PROD, and can be true in 'test', 'development', etc.). Do not assume DEV implies only 'development' mode. When reviewing code and tests, treat DEV as a non-production flag and verify environment-specific logic accordingly. Reference: https://vite.dev/guide/env-and-mode#modes

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 9427
File: src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuFilter.vue:33-33
Timestamp: 2026-03-06T00:53:28.835Z
Learning: When reviewing code, note that the enforce-canonical-classes (better-tailwindcss) rule may auto-fix Tailwind v3 !class-name syntax by converting it to v4 class-name! syntax. Do not treat these auto-fixed class-name! instances as newly introduced issues; the perceived change is in syntax placement, not in usage or intent. This guidance applies across all .vue and .ts files in the repository.

Learnt from: sonnybox
Repo: Comfy-Org/ComfyUI_frontend PR: 9446
File: src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue:45-45
Timestamp: 2026-03-06T01:55:00.013Z
Learning: Treat wrap-break-word as a valid Tailwind CSS utility for overflow-wrap: break-word in Tailwind v4+ projects. Do not flag this class as invalid in any Vue (.vue) or TypeScript (.ts/.tsx) files within the repository (e.g., Comfy-Org/ComfyUI_frontend) or other Tailwind v4+ projects. When reviewing, verify that the class is used to enable word breaking in long text content and reference the Tailwind docs: https://tailwindcss.com/docs/overflow-wrap.

Learnt from: kaili-yang
Repo: Comfy-Org/ComfyUI_frontend PR: 11531
File: src/composables/maskeditor/brushDrawingUtils.ts:14-14
Timestamp: 2026-04-22T04:06:01.353Z
Learning: In the Comfy-Org/ComfyUI_frontend codebase, `knip` is used to detect unused exports. When reviewing TypeScript/TSX code, avoid recommending or introducing exported types/functions/constants that have no concrete external consumers (e.g., they’re only used within the same module). Only recommend exporting when there is an actual external usage outside the module. If something might be needed externally later, keep it non-exported for now and defer exporting until an external consumer is added.

Learnt from: eliheuer
Repo: Comfy-Org/ComfyUI_frontend PR: 11317
File: src/components/appMode/layout/panels/useAppPanelLayout.ts:0-0
Timestamp: 2026-05-07T11:21:56.210Z
Learning: In the Vue 3/TypeScript frontend (ComfyUI_frontend), when LiteGraph mutates/reuses graph node arrays in place, Vue reactivity may not trigger if you only mirror/mutate the existing array. When updating state backed by a shallowRef (e.g., for derived panel/layout data), assign a new array reference when mirroring nodes—e.g., `graphNodes.value = [...(app.rootGraph?.nodes ?? [])]`—so recomputed values like `inputEntries`/`panelRows` rerun on reconfigure.

})
}
12 changes: 12 additions & 0 deletions src/platform/workflow/management/stores/comfyWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/w
import type { MissingModelCandidate } from '@/platform/missingModel/types'
import type { MissingMediaCandidate } from '@/platform/missingMedia/types'
import type { MissingNodeType } from '@/types/comfy'
import type {
BlockRow,
PanelPreset
} from '@/components/appMode/layout/panels/panelTypes'

export interface InputWidgetConfig {
height?: number
Expand All @@ -27,6 +31,14 @@ export type LinearInput = [NodeId, string, InputWidgetConfig?]
interface LinearLayoutConfig {
/** Grid column count. Defaults to 4. */
columns?: number
/** Floating panel position preset (right-dock, float-tr, …). */
panelPreset?: PanelPreset
/** Whether the panel is collapsed to its header. */
panelCollapsed?: boolean
/** Drag-resized dock width, in grid cells. */
panelWidthCells?: number
/** 2D block arrangement inside the panel (rows of blocks). */
panelRows?: BlockRow[]
}

export interface LinearData {
Expand Down
26 changes: 25 additions & 1 deletion src/platform/workflow/validation/schemas/workflowSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,31 @@ const zExtra = z
outputs: z.array(zNodeId).optional(),
layout: z
.object({
columns: z.number().int().positive().optional()
columns: z.number().int().positive().optional(),
panelPreset: z
.enum([
'right-dock',
'left-dock',
'float-tr',
'float-br',
'float-tl',
'float-bl'
])
.optional(),
panelCollapsed: z.boolean().optional(),
panelWidthCells: z.number().int().positive().optional(),
panelRows: z
.array(
z.array(
z.object({
id: z.string(),
kind: z.literal('input'),
entryKey: z.string(),
isMultiline: z.boolean().optional()
})
)
)
.optional()
})
.passthrough()
.optional()
Expand Down
53 changes: 44 additions & 9 deletions src/stores/appModeStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,9 @@ export const useAppModeStore = defineStore('appMode', () => {
flyAnimationId = requestAnimationFrame(tick)
}

// Shared panel position + collapse state — single source of truth for
// the floating inputs panel across App Mode (runtime) and App Builder
// (edit). Moving or collapsing the panel in either view updates both,
// so the builder is WYSIWYG with App Mode by construction. In-memory
// only for now.
// Shared panel position + collapse state — single source of truth so
// moving / collapsing the panel in either view updates both. Persisted
// to the workflow via `extra.linearData.layout` (see watch below).
const panelPreset = ref<PanelPreset>('right-dock')
const panelCollapsed = ref(false)
// Width of the dock panel in grid cells (8 = default 440px). Bumping
Expand Down Expand Up @@ -223,6 +221,14 @@ export const useAppModeStore = defineStore('appMode', () => {
const { inputs, outputs } = pruneLinearData(data)
selectedInputs.value = inputs
selectedOutputs.value = outputs

const layout = data?.layout
if (!layout) return
if (layout.panelPreset) panelPreset.value = layout.panelPreset
if (typeof layout.panelCollapsed === 'boolean')
panelCollapsed.value = layout.panelCollapsed
if (layout.panelWidthCells) panelWidthCells.value = layout.panelWidthCells
if (layout.panelRows) panelRows.value = layout.panelRows
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

function resetSelectedToWorkflow() {
Expand All @@ -238,25 +244,54 @@ export const useAppModeStore = defineStore('appMode', () => {
resetSelectedToWorkflow
)

// Builder-only writes: inputs, outputs, and the block arrangement
// inside the panel. Block reorder is gated to builder by PanelBlockList,
// so panelRows only mutates here.
watch(
() =>
isBuilderMode.value
? { inputs: selectedInputs.value, outputs: selectedOutputs.value }
? {
inputs: selectedInputs.value,
outputs: selectedOutputs.value,
panelRows: panelRows.value
}
: null,
(data) => {
if (!data || ChangeTracker.isLoadingGraph) return
const graph = app.rootGraph
if (!graph) return
const extra = (graph.extra ??= {})
extra.linearData = {
inputs: [...data.inputs],
outputs: [...data.outputs]
const linearData: Partial<LinearData> = (extra.linearData ??= {})
linearData.inputs = [...data.inputs]
linearData.outputs = [...data.outputs]
linearData.layout = {
...(linearData.layout ?? {}),
panelRows: data.panelRows.map((row) => row.map((b) => ({ ...b })))
}
workflowStore.activeWorkflow?.changeTracker?.captureCanvasState()
},
{ deep: true }
)

// Persist panel layout. Fires from both App Mode and Builder so a
// user dragging the panel in either view writes back to the workflow.
watch(
() => ({
panelPreset: panelPreset.value,
panelCollapsed: panelCollapsed.value,
panelWidthCells: panelWidthCells.value
}),
(layout) => {
if (ChangeTracker.isLoadingGraph) return
const graph = app.rootGraph
if (!graph) return
const extra = (graph.extra ??= {})
const linearData: Partial<LinearData> = (extra.linearData ??= {})
linearData.layout = { ...(linearData.layout ?? {}), ...layout }
workflowStore.activeWorkflow?.changeTracker?.captureCanvasState()
}
)

let unwatchReadOnly: (() => void) | undefined
function enforceReadOnly(inSelect: boolean) {
const { state } = getCanvas()
Expand Down