Nodes/Thought Bubble/Thought Bubble
ComfyUI Node

Thought Bubble

A prompt whiteboard that does the string-wrangling for you

By matthewfriedrichs·Created 12 months ago·Updated 5 days ago· 15
Thought Bubble
  • model
  • clip
  • model
  • clip
  • positive
  • negative
  • positive_prompt_text
  • negative_prompt_text
canvas_data{"boxes": [{"id": "default-output-box", "title": "output", "content": "", "x": 100, "y": 100, "width": 400, "height": 300, "displayState": "normal", "type": "text"}], "pan": {"x": 0, "y": 0}, "zoom": 1.0, "gridSize": 100, "showGrid": true, "savedView": null, "theme": {}}

Somewhere around your fifth prompt-travel workflow, the string-concat spaghetti gets old. One box feeds a wildcard node, that feeds another wildcard node, both feed a text concat, and the moment you want a prompt that changes per run you're balancing three seeds and praying. Thought Bubble is the "fine, I'll do it all in one node" answer: an infinite canvas of prompt boxes plus a small command language, all serialized into a single node in your graph.

The pitch is simple - you get a whiteboard instead of a text box. You drag little "thought bubbles" around, reference other boxes, drop in wildcards, LoRAs, negative chunks, even conditional logic, and the node renders the final prompt (and conditioning) for you. The author put it in "Workflow Efficiency" and that's exactly what it is: a way to stop fighting the graph and keep your prompt logic in one editable place. It was announced on r/comfyui in late 2025 and gets little fanfare, which is a shame - for prompt-juggling workflows it genuinely replaces a pile of utility nodes.

How it works

The whole canvas is stored as JSON in the node's canvas_data input. That's the weird part when you first look under the hood: every box, its position, your theme, even the run counter, is one giant serialized string. A JavaScript extension draws the editable canvas over that state in the UI, and a Python parser (CanvasParser) expands it on execution.

Underneath it's a proper little programming language. The commands live right in your prompt text:

  • i(option1 | option2 | option3) - sequential iteration. Picks the next item on each run and loops. This is your prompt-travel engine.
  • w(option1 | option2) - random pick, seeded by the node's seed input. Same seed, same choice.
  • -(text) - moves text from positive to negative.
  • lora(name:model_strength:clip_strength) - loads a LoRA from inside the prompt, with autocomplete in the editor.
  • v(name) - reference another box or a variable, so boxes can compose.
  • ?(keywords | if true | if false) - a weighted conditional. Handy, and more power than most people need on day one.

The iterator is the clever bit. A hook on ComfyUI's queue prompt bumps the run counter by one every time you hit generate, so i() automatically steps forward - no batch-by-batch fiddling. Control boxes add stateful variables (increment, decrement, randomize) that you can link to i() or w() for multiple independent iterators in one prompt.

The inputs and outputs that matter

You'll set two things by hand:

  • seed - controls all randomness. Change it for wildcard variety, keep it for reproducible runs.
  • canvas_data - the entire canvas. You normally never touch this; the UI edits it for you. It's also how your boxes survive a workflow save.

model and clip are optional, and which way you wire them changes the whole game. Leave them unconnected and Thought Bubble is a prompt builder - feed its positive_prompt_text / negative_prompt_text outputs into a normal CLIP Text Encode and carry on like nothing happened. Connect model and clip and it becomes a prompt executor: lora() commands get applied to the model, and you get ready-made positive and negative CONDITIONING outputs you can wire straight into a KSampler. It'll even build masked area conditioning from "area" boxes and timed conditioning for animations. Start unconnected, graduate to connected.

Installing it

No dependencies, no model downloads, no requirements.txt. It's pure Python plus the JS editor:

cd ComfyUI/custom_nodes
git clone https://github.com/matthewfriedrichs/ComfyUI-ThoughtBubble.git

Restart ComfyUI, or skip all that and search "Thought Bubble" in ComfyUI Manager. On first run it auto-creates ComfyUI/user/wildcards/ and ComfyUI/user/textfiles/ - that's where w() and o() look for their files, so if your wildcards "aren't found," check you put them in the right user folder.

Gotchas

  • Don't edit canvas_data by hand. It's JSON; one stray brace and the node silently falls back to an empty canvas. The README even shows the default state - that's for reference, not for pasting.
  • Double-apply LoRAs. If you connect model/clip and still run a LoraLoader on the same model chain, you're stacking effects. Pick one path.
  • The node caches conditioning aggressively, keyed on prompt + clip identity. It's a speed win, but if you ever suspect stale output, bump the seed or change the prompt.
  • After a ComfyUI update, if the canvas stops rendering, check whether the Nodes 2.0 frontend beta got enabled - this is a heavy custom-JS canvas, and the new renderer has been known to break exactly these.
CategoryWorkflow Efficiency

Inputs (3)

NameTypeDefaultDescription
canvas_dataSTRING{"boxes": [{"id": "default-output-box", "title": "output", "content": "", "x": 100, "y": 100, "width": 400, "height": 300, "displayState": "normal", "type": "text"}], "pan": {"x": 0, "y": 0}, "zoom": 1.0, "gridSize": 100, "showGrid": true, "savedView": null, "theme": {}}
modeloptMODEL
clipoptCLIP

Outputs (6)

NameTypeDescription
modelMODEL
clipCLIP
positiveCONDITIONING
negativeCONDITIONING
positive_prompt_textSTRING
negative_prompt_textSTRING