feat: App mode saving rework (#9338)

## Summary

Change app mode changes to be written directly to the workflow on change
instead of requiring explicit save via builder.
Temporary: Adds `.app.json` file extension to app files for
identification since we don't currently have a way to identify them with
metadata
Removes app builder save dialog and replaces it with default mode
selection

## Changes

- **What**: 
- ensure all save locations handle app mode
- remove dirtyLinearData and flushing

- **Breaking**: 
- if people are relying on workflow names and are converting to/from app
mode in the same workflow, they will gain/lose the `.app` part of the
extension

## Screenshots (if applicable)

<img width="689" height="84" alt="image"
src="https://github.com/user-attachments/assets/335596ee-dce9-4e3a-a7b5-f0715c294e41"
/>

<img width="421" height="324" alt="image"
src="https://github.com/user-attachments/assets/ad3cd33c-e9f0-4c30-8874-d4507892fc6b"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9338-feat-App-mode-saving-rework-3176d73d3650813f9ae1f6c5a234da8c)
by [Unito](https://www.unito.io)
This commit is contained in:
pythongosssss
2026-03-03 19:35:36 +00:00
committed by GitHub
parent ab2aaa3852
commit 68b16e3a3f
18 changed files with 842 additions and 406 deletions

View File

@@ -6,6 +6,7 @@ import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { LinearData } from '@/platform/workflow/management/stores/comfyWorkflow'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
export const useAppModeStore = defineStore('appMode', () => {
const { getCanvas } = useCanvasStore()
@@ -28,31 +29,12 @@ export const useAppModeStore = defineStore('appMode', () => {
loadSelections(activeWorkflow.changeTracker?.activeState?.extra?.linearData)
}
function flushSelections() {
const workflow = workflowStore.activeWorkflow
if (workflow) {
workflow.dirtyLinearData = {
inputs: [...selectedInputs],
outputs: [...selectedOutputs]
}
}
}
watch(
() => workflowStore.activeWorkflow,
(newWorkflow, oldWorkflow) => {
// Persist in-progress builder selections to the outgoing workflow
if (oldWorkflow && isBuilderMode.value) {
oldWorkflow.dirtyLinearData = {
inputs: [...selectedInputs],
outputs: [...selectedOutputs]
}
}
// Load from incoming workflow: dirty state first, then persisted
(newWorkflow) => {
if (newWorkflow) {
loadSelections(
newWorkflow.dirtyLinearData ??
newWorkflow.changeTracker?.activeState?.extra?.linearData
newWorkflow.changeTracker?.activeState?.extra?.linearData
)
} else {
loadSelections(undefined)
@@ -61,6 +43,24 @@ export const useAppModeStore = defineStore('appMode', () => {
{ immediate: true }
)
watch(
() =>
isBuilderMode.value
? { inputs: selectedInputs, outputs: selectedOutputs }
: null,
(data) => {
if (!data) return
const graph = app.rootGraph
if (!graph) return
const extra = (graph.extra ??= {})
extra.linearData = {
inputs: [...data.inputs],
outputs: [...data.outputs]
}
},
{ deep: true }
)
watch(
() => mode.value === 'builder:select',
(inSelect) => (getCanvas().read_only = inSelect)
@@ -75,8 +75,6 @@ export const useAppModeStore = defineStore('appMode', () => {
}
async function exitBuilder() {
const workflow = workflowStore.activeWorkflow
if (workflow) workflow.dirtyLinearData = null
resetSelectedToWorkflow()
setMode('graph')
}
@@ -85,7 +83,6 @@ export const useAppModeStore = defineStore('appMode', () => {
enterBuilder,
exitBuilder,
hasOutputs,
flushSelections,
resetSelectedToWorkflow,
selectedInputs,
selectedOutputs