Document centralized node presentation exceptions
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# BV node presentation architecture
|
||||
|
||||
This document is the authoritative contract for BV node and widget presentation.
|
||||
The machine-readable exception registry is `PRESENTATION_EXCEPTIONS` in
|
||||
`ui/src/regional/nodePresentation.ts`.
|
||||
|
||||
## Non-negotiable rules
|
||||
|
||||
1. Every new BV UI surface uses the shared BV UI pack and its tokens/components.
|
||||
2. Every manipulation of a ComfyUI node, port or widget is implemented in the
|
||||
central presentation/manipulation modules.
|
||||
3. Feature modules may own domain state and structural graph behavior. They must
|
||||
delegate hiding, projection, ordering, measurement, compaction and resizing.
|
||||
4. Classic, Node Library Ghost and Nodes 2.0 consume the same semantic policy.
|
||||
5. A connected Legacy port stays visible even when Legacy Debug is disabled.
|
||||
6. Internal state and provider plumbing remain serialized and executable while
|
||||
their visual representation is hidden.
|
||||
7. User-controlled dimensions are never overwritten by an automatic refresh.
|
||||
8. Direct `converted-widget`, `hidden`, `computeSize`, `setSize`, DOM projection
|
||||
or slot-visibility code outside the central modules requires a registered
|
||||
exception before it is merged.
|
||||
|
||||
## Central modules
|
||||
|
||||
| Module | Responsibility |
|
||||
| --- | --- |
|
||||
| `ui/src/regional/nodePresentation.ts` | Semantic roles, node policies and exception registry |
|
||||
| `ui/src/regional/classicNodePresentation.ts` | Classic adapter and measured layout |
|
||||
| `ui/src/regional/nodes2NodePresentation.ts` | Nodes 2.0 DOM adapter |
|
||||
| `ui/src/regional/nodePreviewProjection.ts` | Node Library Ghost adapter |
|
||||
| `ui/src/regional/portProjection.ts` | Runtime provider-port projection and connection-aware layout |
|
||||
|
||||
Node-specific code may request these operations. It must not reimplement them.
|
||||
|
||||
## Registered exceptions
|
||||
|
||||
The current exceptions, their exact implementation paths, rationale and intended
|
||||
centralization path live in `PRESENTATION_EXCEPTIONS`. This keeps the list next
|
||||
to the policy used at runtime and prevents documentation from silently diverging.
|
||||
|
||||
An exception is legitimate only when it adapts a genuinely different ComfyUI
|
||||
lifecycle seam, such as promoted Subgraph proxy widgets or runtime-generated
|
||||
graph slots. A node being visually unusual is not sufficient justification.
|
||||
|
||||
## Adding or changing UI behavior
|
||||
|
||||
Before implementation:
|
||||
|
||||
1. Add or extend the semantic role/policy centrally.
|
||||
2. Extend the applicable Classic, Ghost and Nodes 2.0 adapter tests.
|
||||
3. Use BV UI pack components and tokens for rendered controls.
|
||||
4. If a central interface cannot represent the requirement, deepen that module.
|
||||
5. Only if the lifecycle seam genuinely differs, register an exception with its
|
||||
file path, reason and concrete future centralization path.
|
||||
|
||||
No presentation task is complete until implementation, automated coverage,
|
||||
visual acceptance and this architecture contract agree.
|
||||
@@ -1,4 +1,6 @@
|
||||
// bv_pipe_shared.js
|
||||
// Registered presentation exception: dynamic-pipe-slot-structure.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
export const MAX_SLOTS = 100;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: execution-result-preview-widgets.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../scripts/widgets.js";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: seed-action-projection.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { applySeedAction, materializeSeedControl } from "./bv_seed_model.js";
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: dynamic-pipe-slot-structure.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { moveMarkedPortToEnd, nextFreeOrdinal, promoteConnectedInheritedSlots, promoteInheritedSlot, resolveLocalSlotNameCollisions, retainedMissingSlots, reusableSmartPipePortIndex, smartPipeSlotName, uniqueSmartPipeSlotName, updateSmartPipePort } from "./bv_smart_pipe_slots.js";
|
||||
import { mergePipeSchemas, SMART_PIPE_DEFAULT_TITLE } from "./bv_smart_pipe_merge_model.js";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: dynamic-pipe-slot-structure.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { activeDescriptorFor, collectExpandedPipeAddresses, propagate } from "./bv_smart_pipe.js";
|
||||
import {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: subgraph-promoted-widget-proxies.
|
||||
// See PRESENTATION_EXCEPTIONS in ui/src/regional/nodePresentation.ts.
|
||||
const PRESENTATION_TYPES = new Set(["BV_HEADING", "BV_SPACER", "BV_DIVIDER"]);
|
||||
import { normalizeDynamicComboSelection } from "./bv_dynamic_combo_model.js";
|
||||
const PRESENTATION_NODE_TYPES = new Map([
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: control-center-dynamic-widgets.
|
||||
// See PRESENTATION_EXCEPTIONS in regional/nodePresentation.ts for rationale and exit path.
|
||||
import { getApp } from "../../appHelper.js";
|
||||
import { collectAllGroups, collectNodesByType, nodeMatchesType } from "../../util/control/collector";
|
||||
import { BVControlConfig, CONFIG_CHANGED_EVENT, readConfig, writeConfig } from "../../util/control/configHandler";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Registered presentation exception: m0-runtime-resource-projection.
|
||||
// See PRESENTATION_EXCEPTIONS in nodePresentation.ts for rationale and exit path.
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { getApi, getApp } from "../appHelper.js";
|
||||
|
||||
@@ -1,6 +1,70 @@
|
||||
export type PresentationSurface="classic"|"ghost"|"nodes2";
|
||||
export type PresentationRole="public"|"legacy"|"internalState"|"provider"|"dynamicReserve";
|
||||
|
||||
/**
|
||||
* Central contract for every BV node/widget presentation mutation.
|
||||
*
|
||||
* New UI must use the shared BV UI pack. New code must not hide, resize,
|
||||
* replace, reorder or project ComfyUI nodes/widgets directly outside the
|
||||
* central presentation modules. The entries below document the pre-existing
|
||||
* adapters that still have to touch a ComfyUI-specific lifecycle seam. They
|
||||
* are exceptions to be retired or absorbed here, not examples to copy.
|
||||
*
|
||||
* Full invariants, ownership rules and adapter rationale:
|
||||
* docs/design/node-presentation.md
|
||||
*/
|
||||
export type PresentationException=Readonly<{
|
||||
id:string;
|
||||
implementation:readonly string[];
|
||||
reason:string;
|
||||
centralizationPath:string;
|
||||
}>;
|
||||
|
||||
export const PRESENTATION_EXCEPTIONS:readonly PresentationException[]=[
|
||||
{
|
||||
id:"regional-prompt-bootstrap-measurement",
|
||||
implementation:["ui/src/regional/portProjection.ts#installRegionalPromptCreationLayout"],
|
||||
reason:"ComfyUI measures the backend definition before the Regional Prompt instance has its projected provider markers and action widgets.",
|
||||
centralizationPath:"Keep this lifecycle adapter until the central presentation inventory can run before the first ComfyUI measurement.",
|
||||
},
|
||||
{
|
||||
id:"m0-runtime-resource-projection",
|
||||
implementation:["ui/src/regional/m0ResourceSpike.tsx"],
|
||||
reason:"The M0 collector and consumers create graph-bound provider slots and DOM pickers dynamically, before the normal policy inventory is complete.",
|
||||
centralizationPath:"Move its local widget hiding and slot flags behind central widget/port projection helpers before extending M0.",
|
||||
},
|
||||
{
|
||||
id:"control-center-dynamic-widgets",
|
||||
implementation:["ui/src/components/control/bv_control_center.ts"],
|
||||
reason:"Control toggles and the conflict-status row are generated from runtime configuration and must be reconciled in a stable order.",
|
||||
centralizationPath:"Keep dynamic widget creation local; move all hiding, sizing and ordering primitives into the central manipulation library.",
|
||||
},
|
||||
{
|
||||
id:"subgraph-promoted-widget-proxies",
|
||||
implementation:["js/bv_subgraph_ui_projection.js","js/bv_subgraph_nodes2_projection.js","js/bv_subgraph_layout.js"],
|
||||
reason:"Promoted Subgraph widgets are ComfyUI-owned proxy objects that are recreated asynchronously and differ between Classic and Nodes 2.0.",
|
||||
centralizationPath:"Keep the lifecycle adapter separate, but route shared visibility and sizing decisions through this module and the presentation bridge.",
|
||||
},
|
||||
{
|
||||
id:"dynamic-pipe-slot-structure",
|
||||
implementation:["js/bv_pipe_shared.js","js/bv_smart_pipe.js","js/bv_smart_pipe_merge.js"],
|
||||
reason:"Pipe nodes add, remove and relabel structural graph slots from their runtime schema; this is domain behavior, not merely visual projection.",
|
||||
centralizationPath:"Structural slot ownership remains local; every visual hide, compact or resize operation must use the central presentation bridge.",
|
||||
},
|
||||
{
|
||||
id:"seed-action-projection",
|
||||
implementation:["js/bv_seed.js"],
|
||||
reason:"Seed actions are semantic controls that also project through promoted Subgraph widgets and therefore require a source/host adapter.",
|
||||
centralizationPath:"Keep seed state transitions local and route widget construction, sizing and host projection through shared presentation primitives.",
|
||||
},
|
||||
{
|
||||
id:"execution-result-preview-widgets",
|
||||
implementation:["js/bv_prompt_debug.js"],
|
||||
reason:"Prompt AST Debug and Text Log Writer expose read-only multiline content populated only by execution callbacks.",
|
||||
centralizationPath:"Keep execution data handling local; create and style preview widgets exclusively through the shared BV UI/presentation helpers.",
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type PresentationPort=Readonly<{
|
||||
direction:"input"|"output";
|
||||
name:string;
|
||||
|
||||
@@ -16,6 +16,8 @@ export function markProjectedProvider(slot:any){if(!slot)return;slot.hidden=true
|
||||
|
||||
export function suppressInitialProjectedProviderDefinitions(nodeData:any){const optional=nodeData?.input?.optional;if(!optional)return[];const removed:string[]=[];for(const [name,spec]of Object.entries(optional)){if(PROVIDERS.has(String((spec as any)?.[0]??""))){delete optional[name];removed.push(name)}}return removed}
|
||||
|
||||
// Registered presentation exception: regional-prompt-bootstrap-measurement.
|
||||
// See PRESENTATION_EXCEPTIONS in nodePresentation.ts for rationale and exit path.
|
||||
export function installRegionalPromptCreationLayout(nodeType:any){const prototype=nodeType?.prototype,original=prototype?.computeSize;if(!prototype||typeof original!=="function"||prototype.__bvRegionalPromptCreationLayout)return;prototype.__bvRegionalPromptCreationLayout=true;prototype.computeSize=function(){if(this.__bvRegionalPromptUiReady)return original.apply(this,arguments);const view=measurementView(this,{inputs:(this.inputs??[]).filter((slot:any)=>!internal(slot)),outputs:(this.outputs??[]).filter((slot:any)=>slot?.name!=="lora_bindings"),widgets:[{type:"button",name:"open_regional_editor",computeSize:()=>[0,20]},{type:"button",name:"quick_edit_regional_prompts",computeSize:()=>[0,20]}]});return original.apply(view,arguments)}}
|
||||
|
||||
export function retainNeededProjectedInputs(node:any,wantedNames:string[],matches:(slot:any)=>boolean){const wanted=new Set(wantedNames);for(const {slot,index} of (node.inputs??[]).map((slot:any,index:number)=>({slot,index})).filter(({slot}:any)=>matches(slot)&&!wanted.has(String(slot.name))&&slot.link==null).sort((a:any,b:any)=>b.index-a.index))node.removeInput?.(index);for(const name of wantedNames){const slot=node.inputs?.find((item:any)=>item.name===name);if(slot)markProjectedProvider(slot);}}
|
||||
|
||||
Reference in New Issue
Block a user