Files
DumiFlex d34fc8f8cf feat(tutorials): in-depth Context Loop, Context Injector and Image Filter videos (#352)
* feat(tutorials): in-depth Context Loop, Context Injector and Image Filter videos

N05 walks the whole Context Loop (frames vs batch size vs Run count, Edit
frame chips, Override Context seed, strategies, the seeds panel, Ctrl/Alt
click, values, a rule on $iteration, Seed List, Hold, Bypass loop). N04
takes the Context Injector apart and N12 the Image Filter (labels, prompt
edits with clip, the mask painter, Compare, Zoom, Send to Loop, the node's
settings). The old Nodes videos move to their new numbers (Debug N03,
Label & Grid N13, Note N14, Bookmark N15); N12 replaces the old N01.

* fix(tutorials): tick both rows in T03's bulk edit

The selection bar slides in after the first tick and moves the rows, so the
second click could land where its row used to be. Tick one row at a time and
check each one took.

* fix(tutorials): scroll T05's rules into view before boxing them

scrollTo now falls back to scrolling the part's own scrolling box when the
wheel doesn't move it, and T05 scrolls each branch (and the second action)
to the upper screen before its callout, so the boxes are on screen.

* fix(tutorials): narrate T06's canvas build so it plays at normal speed

The two builds had no line, so the render squeezed them to 4.5x and the
hand looked rushed. A line over each keeps them at normal speed.

* fix(tutorials): a hand at work plays at most 2x in the render

The Tutor now records when the hand clicks, drags, types or presses a key
(cues.json acts), and render.mjs speeds a silent stretch with hand work up
by at most 2x instead of up to 8x, so builds don't look rushed. The ten
longest silent builds (T04, A04, A05, A06, A09, A10, A12, L03 and two in
N14) get a line, so they play at normal speed.

* fix(tutorials): give N04's Injector room beside the stand-in nodes

The Injector landed on the first text box and covered its output, so the
first wire never connected. The stand-ins sit further right, the Injector
further right still, and the canvas slides left before the Context step.

* fix(tutorials): keep both ends of N12's new wires on screen

The mask wire was dragged from the filter to a node that had scrolled off
the top, so it dropped on empty canvas. The canvas now fits the nodes of
each wire into the free part of the screen first, and the new Set Latent
Noise Mask goes under the refine sampler.

* fix(tutorials): N05 and N04 fixes from the first takes

N05: zoom in on the Seed List while it's talked about (it sat under the
caption), and drop the callout labels that covered the frame chips and the
template. N04: narrate the Context / Assembler / preview build (36 s of
silence) and move the hand off the rows so the globe tooltip closes.

* fix(tutorials): paint N12's mask on the painter's canvas

In paint mode the mask painter replaces the detail image, so the strokes
aimed at an image that was no longer there.

* fix(tutorials): a single click doesn't hold a long wait at 2x

A Run click followed by a long wait counted as hand work, so the wait played
at 2x instead of being squeezed. A stretch now needs two or more hand
actions to be capped.

* fix(tutorials): tuck N12's picker away with its minus button

Escape in Detail goes back to the grid instead of tucking the picker away,
so the waiting pill never showed.

* fix(tutorials): box N12's two extra slots as a pair

The callout took one locator matching both slot dots, which strict mode
refuses.

* fix(image-filter): keep the picker's keys after a view change

Switching mode or leaving the painter removed the focused control and dropped
focus to body, so the picker's keys went to the canvas bookmarks. Focus goes
back to the picker. N12 hands the keys back before Compare and Zoom.

* fix(tutorials): L05 waits for Role's save to store a history entry

* fix(tutorials): a few clicks across a long wait don't cap the squeeze

* fix(tutorials): review 2026-11-11 first half (lines over empty canvas, late boxes, N12 framing)

* fix(tutorials): review 2026-11-11 A09-A19 findings

* fix(tutorials): review 2026-11-11 L03, L05, N14, E4 findings; jumpTo keeps tall notes above the caption

* fix(tutorials): A06 and T05 build their Context before the line

* fix(tutorials): N14 keeps the label's peek open through its line

* fix(tutorials): N12 boxes the extra slot rows; callouts fall back to the raw box

* fix(tutorials): N14 hovers the label before its line

* fix(tutorials): N14 keeps the hover card up while its line plays
2026-11-11 03:33:14 +00:00

84 lines
4.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Video tutorials, recorded by Watchwright: each test is one tutorial and its video. A test drives
// the real ComfyUI canvas and manager with a visible pointer, captions and callouts (tutorial.ts),
// and saves its narration cues; render.mjs turns the take into the finished MP4 with the voice
// (voice/speak.py), subtitles and chapters. See README.md.
import path from "node:path";
import { pack } from "watchwright";
import { benchHelpers } from "../bench/bench.js";
import { lifecycleAfterTask, lifecycleSetup, lifecycleTasks, lifecycleTeardown } from "../bench/lifecycle.js";
import { cursorScript } from "./tutorial.js";
const fixture = (packDir: string) => path.join(packDir, "library.json");
// The ComfyUI the takes record. Set WP_TUTORIALS_URL before starting Watchwright to record a
// clean ComfyUI on another port (see README › Record on a clean ComfyUI).
const baseURL = process.env.WP_TUTORIALS_URL ?? "http://127.0.0.1:8189/wp/";
export default pack({
name: "Wildcard Pipeline tutorials",
description: "Records the video tutorials: the canvas and the manager clicked through with a steady pointer, captions and callouts, one video per tutorial.",
// The bench's site key: a recording never runs while a bench run changes the library.
site: { key: "wildcard-pipeline", baseURL },
browser: {
// Chromium: Firefox's screencast drops frames. 1920 × 1080 is the video's size, since the
// screencast records CSS pixels (a device scale factor doesn't make it sharper).
name: "chromium",
viewport: { width: 1920, height: 1080 },
context: { colorScheme: "dark" },
},
params: {
reset: {
type: "boolean",
default: true,
description: "Load the tutorial library (library.json) first, so every take starts from the same items. Your library is backed up first and put back after.",
},
putBack: { type: "boolean", default: true, description: "Put your library back when the run ends." },
showcase: {
type: "boolean",
default: false,
description: "End tutorials with real images: the prompt goes into a checkpoint and sampler and a few runs land on screen. Needs a GPU and the checkpoint below.",
},
unet: {
type: "string",
default: "flux3/flux3-image-klein-int8-convrot.safetensors",
description: "A diffusion model in models/diffusion_models (Flux 3 Klein by default). When set, the showcase loads it with its own text encoder and VAE below, at cfg 1 with an empty negative; leave it empty to load the checkpoint instead.",
},
clip: { type: "string", default: "qwen3vl_4b_int8_convrot.safetensors", description: "The text encoder for the diffusion model, in models/text_encoders." },
clipType: { type: "string", default: "flux3", description: "The CLIP loader's type for that text encoder." },
vae: { type: "string", default: "flux2-vae.safetensors", description: "The VAE for the diffusion model, in models/vae." },
checkpoint: {
type: "string",
default: "",
description: "A checkpoint in models/checkpoints to load instead of the diffusion model (when unet is empty), e.g. an SDXL model.",
},
exampleCheckpoint: {
type: "string",
default: "juggernautXL_ragnarokBy",
description: "The SDXL checkpoint the example workflows (E1-E4, N12, N13) load, since they are built on a checkpoint loader. The `checkpoint` param wins when it is set.",
},
steps: { type: "number", default: 12, description: "Sampler steps for the showcase (Klein: 8 to 16)." },
cfg: { type: "number", default: 1, description: "CFG for the showcase (1 for Klein; about 5 for an SDXL checkpoint)." },
width: { type: "number", default: 1024, description: "Image width for the showcase." },
height: { type: "number", default: 1280, description: "Image height for the showcase." },
},
// One take per tutorial, every take keeps its video, no robot labels in the picture. High quality
// (Watchwright 0.16+): JPEG 100 frames, every frame kept, encoded after the take; 60 fps so the
// pointer (moved every 16 ms) glides; followVideo keeps the picture on whichever tab is in front.
run: {
video: "always", videoQuality: "high", videoFps: 60, followVideo: true,
showActions: false, chapters: false, trace: "problems", pauseLimitMinutes: 5, testTimeout: 15 * 60_000,
},
helpers: benchHelpers,
async setup(ctx) {
const { h, context } = ctx;
await context.addInitScript({ path: cursorScript(ctx.packDir) });
h.page.on("dialog", (d) => void d.accept().catch(() => undefined));
await h.visit("wildcards");
await lifecycleSetup(ctx, { fixture });
},
teardown: lifecycleTeardown,
afterTask: lifecycleAfterTask,
tasks: lifecycleTasks(fixture),
});