* feat(nodes): let a link drive the prompt text of every PromptManager node
The Focused, Split and Pair editors hide their widgets, and ComfyUI only
draws a widget's input socket while the widget is visible. `text` (and the
Pair's `negative_text`) therefore had no socket, and a STRING link dropped
on the editor fell through to the first free STRING input, `prepend_text`,
which is how #342's workflow was wired.
- Detach those inputs from their hidden widgets on create and after a
workflow load, so they are ordinary sockets in Classic and Nodes 2.0. The
schema, widget order and saved values are unchanged.
- A STRING link dropped on the editor body fills the main prompt, then the
negative, before the frontend's own choice.
- Each editor says when its text comes from a link; the typed text is kept
for disconnect. The Classic nodes now save the typed text under a link
instead of null, and "use prompt" no longer overwrites it while linked.
- Real-host tests: mouse drags in both renderers, a workflow saved before
this change, and executions proving the linked text is saved, counted,
recorded in history and linked to the run's image.
Closes#343
* test(coverage): floor for the linked-text module
* test(nodes): PromptSearchList text takes a dragged link like a core widget
* fix(nodes): linked text review round for #343
- A legacy frontend Primitive linked to an editor's text or the Pair's
negative now runs with its value: the graphToPrompt wrapper and the queue
hook fill it in (through virtual reroutes), in new and pre-#343 workflows.
- Moving a linked text and releasing it on the editor keeps it on `text`:
a link being moved counts as free, as the frontend counts it.
- A link dropped on a prompt box (or its link note) fills that box's input,
so the Pair's Negative prompt box takes `negative_text` (Classic through
getInputOnPos, Nodes 2.0 through the box's slot key).
- Only exact STRING links are steered; wildcards keep the frontend choice.
- A linked Classic text box gives its row back and gets it back on
disconnect; the PromptManagerText note uses the theme colour.
- The metadata parsers map positional widget values through the widget
table when an editor's widget-bound inputs do not line up with it.
- Real-host cases for the legacy Primitive (new and saved workflows, a real
Run), the moved link, the Negative box, paste/clone/undo and Split.
* fix(nodes): resolve a legacy Primitive's text like the host, once per run (#343)
- Inputs still bound to their widget (Classic PromptManager/PromptManagerText)
are left as the host resolved them; the injection no longer overwrites them.
- For the editors' detached text inputs, the Primitive's value goes through its
"Run widget replace on values" replacement and the hidden widget's own
serializer, so {a|b} wildcards and %date% tokens resolve as on main.
- Each queued output is filled once (graphToPrompt wrapper, or the queue hook
for an output that did not come through it): a wildcard is drawn once.
- Editors inside subgraphs are found under their <subgraph>:<inner> id.
- Real-host runPrompt installs its request route before the run starts.
- Shared parser case pins the widget-table fallback for a core KSampler.
* fix(nodes): pin the Classic wildcard behaviour and match queued ids by type (#343)
- Classic PromptManager/PromptManagerText expose _pmTextSerializerReady, settled
once their own text serializer is installed; the real-host specs wait on it
and assert the Classic text is queued as written, as on main.
- The Primitive fill only touches an output entry whose class_type matches the
graph node at that id.
- TESTING.md notes the host's harmless "Invalid widget" warning per Run.
ComfyUI stamps "is_changed": [NaN] on nodes that always re-run, the
PromptManager node among them. The legacy gallery and admin pages replaced
NaN with null before JSON.parse, so they traced these files' prompts. The
scan's fallback parsed records strictly, so every NaN-stamped file the
resolver could not decide (hires-fix and refiner chains, custom latent
nodes) fell to "Needs review" although legacy linked its prompt.
parse_page_json parses as the legacy pages did, but only the NaN number
reads as null: the word inside prompt text is kept. Infinity still makes
the record unreadable, as it did in the pages.
utils.generation_params converted nested arrays to strings recursively,
so a crafted file with a value nested about 900 arrays deep raised
RecursionError, where the browser parser returns a result. The join now
runs on an explicit stack. A JS-parity case with 900-deep values pins it.
* feat(parse): port the browser generation-parameter parser to Python
Adds utils/generation_params.py, a line-for-line port of
extractGenerationParams in web/js/comfy-metadata.js, the parser behind
the gallery, admin and metadata pages. The scan and live monitor can use
it so every surface reads prompts the same way. The JavaScript parser is
unchanged.
The port reproduces the JavaScript value semantics the result depends on
(property order, truthiness, String(), trim(), strict equality, Map keys,
JSON.parse number rounding, and the inputs on which it throws), and runs
its recursive tracing on an explicit stack so crafted graphs cannot
exhaust the Python stack.
Tests:
- behaviour tests for each tracing rule, with JavaScript results as
expected values, including the visit-limit boundary;
- golden vectors of the JavaScript parser's results for every named case
and every PNG fixture, so the comparison runs without Node;
- a live comparison against the JavaScript parser when Node is present;
- a randomized differential test over 9,000 generated graphs and
workflows, coherent and corrupted, including inputs where the
JavaScript throws.
Five deliberate porting mistakes (trim, truthiness, prepend handling,
visit limit, pair slot) each fail the tests. On a 38,602-file output
folder, all 18,491 files with a workflow give identical results.
* fix(scan): link the traced prompt when the pair resolver cannot decide
The output scan skipped every file the pair resolver called ambiguous or
unresolved, so workflows where several samplers share one prompt (a base
pass, an upscale pass and a detailer) never reached the Library from a
scan. It now links the positive prompt the shared parser traces for
those files, the prompt the inspector and the legacy pages show. Files
the resolver decides are stored as before, and graphs without a
recognized sampler keep the legacy extractor (#252).
Read-only scans of a 38,602-file output folder into temporary databases:
- links: 1,500 before, 7,954 after; none of the 1,500 dropped or changed;
- of the added links that differ from the pre-redesign scan, 1,275
replace a number, a LoRA tag, a fragment or a truncated prompt that
scan stored.
* refactor(parse): return a uniform pair from the SameValueZero key