A native ComfyUI workflow editor built on vibekit, targeting .NET 10 with Dear ImGui.
Read order for new agents. This README →
git log --oneline -n 40in this repo →vibekit/README.mdif you'll touch the platform layer →Readfiles only after that.
Prerequisites: .NET 10 SDK
Sibling dependency: oomfi depends on vibekit via path reference. Clone it alongside:
git clone https://github.com/holo-q/vibekit ../vibekitBuild:
dotnet buildThe earlier README claimed Oomfi.Client / Oomfi.Graph / Oomfi.Gui / Oomfi.Session / Oomfi.Prefs as separate projects. They are not. The
real layout is:
Oomfi (executable) composition root, GUI shell, windows, chrome, prefs UI
↓
Oomfi.Platform (library) ComfyUI SPI: Net (HTTP+WS), Graph, Render, Ids, Workspace
↓
Vibekit (Core, Platform, Widgets.{Canvas,Nodes}, Tracing, sourcegen analyzers)
Slnx: Oomfi.slnx. Both projects target net10.0.
Program.cs AppKit.Launch(new OomfiApp())
↓
OomfiApp.OnSetup() (in OomfiApp.cs)
1. Prefs migrate (ServerConfig, Editor, Theme, Keybinds, ...)
2. Build OmfiKeybindStore (generated [Command] actions + persisted overrides)
3. CreateWorkspace() → OomfiWorkspace ; bootstrap blank tab
4. New ComfyNet (client + WSListener + node/model catalogs)
5. Build HistoryUI shared context
6. CreateWindows() — 12 long-lived BaseWindows (see Windows table below)
7. menu.AddProvider(this) (sourcegen-emitted RegisterGeneratedMenu)
8. chrome.AddProvider(execution + theme providers)
9. theme.Apply(prefs)
10. net.StartListening() (WS loop)
11. if ServerConfig.AutoConnect → net.Connect()
OomfiApp is split into 9 files, one concern each. Mirrors the Python
mixin pattern (app_*.py).
| File | Owns |
|---|---|
OomfiApp.cs |
Composition root: ctor, OnSetup, prefs roots, Quit, accessors |
OomfiApp.Handlers.cs |
AppKit lifecycle: OnPersistState, OnFrameBegin, OnGUI, OnShortcuts, OnCleanup, OnFileDrop |
OomfiApp.Menu.cs |
[Command]-decorated handlers: Open/Save/SaveAs/NewWorkflow/DeleteTab/... + GUI_MenuTabs strip |
OomfiApp.Layouts.cs |
BuiltinLayoutBlueprints (Nodes / Bench presets), per-window config, layout migration |
OomfiApp.Windows.cs |
Window field slots + CreateWindows() builder |
OomfiApp.Workspace.cs |
File I/O state, AutoLoadStartupFile, Open, OpenFromPng, SaveAs, drag-resolve |
OomfiApp.NodeRender.cs |
Node renderer lifecycle: BuildNodeRenderer, RefreshNodeRenderer, LiveEditorWindow, tab activation hook |
OomfiApp.Dnd.cs |
File-drop classification + modifier capture (JSON→workflow, PNG→workflow-or-media, image→LoadImage) |
OomfiApp.Media.cs |
Image artifact store wiring, texture lookup, reveal routing |
Implements: AppKit, IKeybindProvider, IActionProvider, IMenuProvider.
src/Oomfi.Platform/
├── OomfiWorkspace.cs Workspace<WorkflowTab> ; tab/artifact/prompt routing
├── WorkflowTab.cs one open workflow + its artifacts + run history + fallback schemas
├── Graph/ workflow model + serde
│ ├── WorkflowGraph.cs the NodeGraph that Vibekit.Widgets.Nodes renders
│ ├── WorkflowIO.cs api-format ↔ web-format ↔ disk
│ ├── ComfyPromptDocument.cs parsed JSON envelope
│ ├── ComfyPromptNode.cs (ClassType, Inputs, Title, Flags) record
│ ├── ComfyValue.cs sealed-record sum: Null|Bool|Number|Text|Array|Object|Link
│ ├── ComboHelpers.cs / JsonHelpers.cs / TypeConversion.cs / ComfyPinMetadata.cs / WorkflowNodeFactory.cs
├── Ids/ strong-typed identities — implicit string in, explicit string out
│ ├── WorkflowId.cs tab/workflow handle
│ ├── PromptId.cs one ComfyUI execution
│ ├── NodeId.cs node within a workflow
│ └── ArtifactId.cs generated image (10-char GUID via .New())
├── Net/ (was monolithic; reorganized 2026-04-27 by transport)
│ ├── Catalog/ NodeCatalog (object_info schema), ModelCatalog (checkpoints/...)
│ ├── Client/ ComfyClient (HTTP + WS), WSListener (semantic event dispatcher)
│ ├── Http/ typed schema records + result records (renamed from "Responses" 2026-04-27)
│ ├── Ws/ ComfyWsMessage hierarchy, BinaryType enum, RunError
│ ├── Runs/ QueueController, PromptRun, RunMode, ExecutionProgress
│ ├── Media/ ImageArtifactStore, NodeImageStore, pending-* async picker types
│ └── Utils/ AsyncThread
├── Render/ ComfyUI-flavoured node rendering (lives outside Vibekit.Widgets.Nodes)
│ ├── ComfyNodeRenderer.cs INodeRenderer impl ; missing-node badges, gallery drops, balloons
│ ├── ComfyParamRenderer.cs widget dispatch (multiline editor state owned per renderer)
│ ├── ComfyDynamicComboRender.cs schema-driven combo boxes
│ ├── ComfyWidgets.cs / ComfyLayout.cs / ComfyRenderImage.cs / ComfyPinControls.cs
│ └── INodeParamHost.cs / ComfyDelegateTracing.cs
└── WorkflowTab.cs / OomfiWorkspace.cs (top-level)
src/Oomfi/
├── Program.cs AppKit.Launch(new OomfiApp())
├── OomfiApp.*.cs (9 partials — see table above)
├── CommandAttribute.cs [Command(path, Icon=..., EnabledWhen=...)]
├── GlobalUsings.cs Vec2/3/4, Mat4, ig=ImGui, TraceLogger, Notifier, Prefs aliases
├── Chrome/ execution chrome, theme, keybind store, queue+runner gizmos
│ ├── OmfiKeybindStore.cs / OmfiTheme.cs / Theme.cs
│ ├── OmfiExecutionChromeProvider.cs / OmfiThemeChromeProvider.cs
│ ├── QueueGizmo + Config + Placement + PeekState + TopToolbarMode
│ ├── RunnerGizmo + Config
│ ├── CanvasChromeRouting.cs / NodeToolsGui.cs
├── Prefs/ sealed prefs sections + Tabs/ UI
│ ├── ServerConfig / EditorPrefs / GalleryPrefs / KeybindPrefs / OmfiThemePrefs
│ ├── PerformancePrefs / StartupPrefs / WorkflowSessionPrefs
│ └── Tabs/ — AppearanceTab, EditorTab, ServerTab, KeybindsTab, PerformanceTab + PreferenceTabs.cs builder
├── Session/ WorkflowNaming.cs (skeleton; persistence not yet ported)
├── Widgets/ HistoryUI.cs (shared timeline embedded in 3 windows)
└── Windows/ 12 docked windows (see table)
| Window | Concern |
|---|---|
NodesWindow |
Multi-tab NodeEditorWidget + node palette sidebar + toolbar gizmos |
QueueWindow |
/queue poll, running/pending list, errors, clear/interrupt |
RunnerWindow |
RunMode (Manual/Queue/AutoQueue) + BatchCount + cached/executed counters |
BenchWindow |
MediaCanvasWindow subclass — active workflow's artifact, pan/zoom |
PreviewWindow |
live-preview frames as they arrive |
HistoryWindow |
HistoryUI instance (scope: global/workflow ; presentation: table/strip) |
DrawerWindow |
Per-node param editor via ComfyParamRenderer |
PropertiesWindow |
Node metadata (type, category, ...) |
ConsoleWindow |
Execution messages / warnings / errors |
GalleryWindow |
Filesystem/artifact browser; drag image to LoadImage pin |
ModelBrowserWindow |
Browse available checkpoints/models from ModelCatalog |
PreferencesWindow |
Vibekit generic prefs window + PreferenceTabs.Build() tab list |
1. user clicks Queue → OomfiApp.Menu.Queue() / RunnerWindow
2. enqueue → ComfyNet.Enqueue → QueueController.Enqueue
3. POST /prompt → ComfyClient.PostPrompt (Net/Client/ComfyClient.cs)
4. server sends WS frames → ComfyClient WS loop
5. parse semantic events → WSListener (Net/Client/WSListener.cs)
6. fan to GUI → QueueWindow / RunnerWindow / NodesWindow
7. binary preview frame → ComfyClient OnPreview → ImageArtifactStore.StageLivePreview() (Net/Media/)
8. result image fetched (/view?filename=…) → ImageArtifactStore.StageResultImage()
9. drain pending textures on frame → ImageArtifactStore.Drain() (called from OomfiApp.OnGUI via OomfiApp.Media.cs)
| Want to add… | Where |
|---|---|
| Menu item w/ keybind hint | [Command("File/Foo", Icon="fa-x")] void Foo() { } in any OomfiApp.*.cs partial |
| Plain menu item (no action) | [Menu("View/Foo")] void Foo() { } likewise |
| Plain keybind | [Shortcut("ctrl+s")] void Save() { } likewise |
| New window | Add file under Windows/ subclassing BaseWindow, BaseWindow<TState>, or BaseWindow<TPrefs,TState>; inject exact collaborators in OomfiApp.Windows.cs |
| New ComfyUI WS message type | Net/Ws/Messages/... + handler in WSListener |
| New prefs section | Sealed class in Prefs/; tab in Prefs/Tabs/; register in PreferenceTabs.Build |
| New layout preset | Add to BuiltinLayoutBlueprints in OomfiApp.Layouts.cs |
| New gallery / drag behavior | OomfiApp.Dnd.cs (Classify + modifier dispatch) |
Trajectory at 2026-04-27. The structure above is correct; these specific surfaces are mid-rename and will move:
- "Surface" vocabulary purge —
Remove stale surface vocabulary(6ee2807),Rename history surface context(abc43ba),Rename Comfy parameter surface renderer(bd3a6c7). Don't introduce*Surfacenames. - History → image artifacts —
Rename output history to image artifacts(aa46494),Separate live previews from output history(04edc64),Rename history actions to artifacts(4a69f19). The Python-era word "output history" is gone. - HTTP "Responses" → "Results" —
Rename Comfy HTTP responses to results(bc68f9b). - Node-definition providers — schema lookups routed through
INodeDefinitionProvider(vibekita9e23bf/6260b85). Tab boundary owns fallback schemas (1575972/819987b). Active provider concept still settling —Use active node provider for model targets(1160b3e). - Typed runtime IDs —
Type websocket runtime ids(f14a838),Type WorkflowGraph internals(ac80274), and many sibling commits in vibekit. New code should use the typed-key types, never strings. - Workspace tab boundary — fallback schemas + media + outputs all migrating onto the
WorkflowTab(6c2eb4d,a54ac97,4ff36c4,819987b). If you're tempted to put per-workflow state onOomfiApp, put it on the tab instead. - Runner / runtime injection —
Inject runner window server(70a7355),Clarify runner server wiring(19f73a7),Collapse runner execution view into ComfyNet(a84f1c1). The runner-vs-net seam is still being narrowed. - AppKit
Connect*collapse —follow AppKit Connect* collapse(43a79c4). OldConnectLayout/Menu/...ceremony was deleted; everything goes throughOnSetup+ interface auto-wire now. - Property→field demotion /
State→state— public-data-class fields, not properties; lowercasestatefield onBaseWindow<TState,_>. Many small commits Apr 24.
- Python source:
../../repositories/oomfi/src/oomfi/— authoritative for parity gaps; we're translating, not rearchitecting (see rootAGENTS.md). - Vibekit platform:
../vibekit/README.md. - ComfyUI mirrors (read-only) under
references/ComfyUI/andreferences/ComfyUI_frontend/— for understanding the upstream protocol surface.