Drop internal plan references from code comments

The pack cited internal planning-doc items (plan P3-7, decision D1, plan
risk 9.2) that aren't public, leaving outside contributors with pointers
they can't follow. Each comment already states its reason; only the
dangling citation is removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Behzad
2026-07-26 23:18:25 +02:00
co-authored by Claude Sonnet 5
parent cca9661dd1
commit f8e5548b46
14 changed files with 82 additions and 84 deletions
+15 -16
View File
@@ -7,12 +7,12 @@ link ids, and slot indices across all of them — the kind of bookkeeping that i
silently wrong until someone drags the file onto a canvas. This builds them from
a description instead, so the ids are correct by construction.
Layout policy (plan P2-1): node positions are never guessed. Every node's height
Layout policy: node positions are never guessed. Every node's height
is estimated from its widget count, and nodes stack per column with a fixed
vertical gap — so no template can ship with overlapping nodes at default sizes.
A test asserts that invariant on the committed files.
Instruction policy (plan P2-2): user-facing instructions live in visible core
Instruction policy: user-facing instructions live in visible core
`Note` nodes on the canvas. `extra.styleref.notes` carries only a one-line
provenance string — ComfyUI never renders it, so it must not carry anything a
user needs to read.
@@ -35,7 +35,7 @@ _PACK_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _PACK_ROOT not in sys.path:
sys.path.insert(0, _PACK_ROOT)
# Single-sourced from the node itself (D4: the Facets output list is data-driven
# Single-sourced from the node itself (the Facets output list is data-driven
# and long — duplicating it here would guarantee drift).
from styleref_nodes.facets import StyleRefFacets # noqa: E402
@@ -89,7 +89,7 @@ INPUTS = {
"PreviewAny": [("source", "*")],
}
# ── layout constants (plan P2-1) ─────────────────────────────────────────────
# ── layout constants ─────────────────────────────────────────────────────────
NODE_WIDTH = 320
HEADER_H = 60 # title bar
@@ -106,10 +106,10 @@ MULTILINE_WIDGETS = {
"Note": {0},
}
# Node titles longer than this truncate in both frontends (plan P2-2).
# Node titles longer than this truncate in both frontends.
MAX_TITLE = 28
# Frontend model-download metadata (plan P2-7): a missing checkpoint becomes a
# Frontend model-download metadata: a missing checkpoint becomes a
# guided download instead of a red error.
# Placeholder ref for the "your own style" templates. Refs are opaque ids now
# (no name lookup), so the templates ship an obvious placeholder the user swaps
@@ -159,7 +159,7 @@ MODELS_ZIMAGE = [MODEL_ZIMAGE_UNET, MODEL_ZIMAGE_CLIP, MODEL_ZIMAGE_VAE]
def node_height(node_type: str, widgets: list[Any]) -> int:
"""
Estimated on-canvas height (plan P2-1): header + one row per input/output
Estimated on-canvas height: header + one row per input/output
slot + one row per widget. The slot term matters for wide-output nodes —
Facets alone has dozens of outputs.
"""
@@ -231,7 +231,7 @@ class Graph:
return self._node_id
def note(self, col: int, text: str, title: str = "Read me") -> int:
"""A visible core Note node — the instruction channel (plan P2-2)."""
"""A visible core Note node — the instruction channel."""
# Height scales with the text so long notes don't clip at default size.
h = HEADER_H + 30 + 18 * (text.count("\n") + len(text) // 55)
return self.place("Note", col, [text], title=title, height=h)
@@ -280,7 +280,7 @@ def apply_widgets(subject: str, target: str) -> list[Any]:
def sampler_widgets(cfg: float) -> list[Any]:
# Seeds randomize (plan P2-4): a fixed seed makes the second queue a silent
# Seeds randomize: a fixed seed makes the second queue a silent
# cache no-op — deadly in a demo.
return [0, "randomize", 25, cfg, "euler", "normal", 1.0]
@@ -332,10 +332,9 @@ def _facet_board(graph: Graph, load: int, col: int) -> int:
def _preview(graph: Graph, apply_node: int, col: int, suffix: str = "") -> None:
"""
Compiled prompts made visible (plan P2-3, extended to every template by
owner decision): Preview Any on Apply's positive and negative kills the
"empty text box on the encoder looks broken" perception and teaches what
StyleRef actually produces.
Compiled prompts made visible, in every template: Preview Any on Apply's
positive and negative kills the "empty text box on the encoder looks
broken" perception and teaches what StyleRef actually produces.
"""
pos = graph.place("PreviewAny", col, [], title=f"Compiled positive{suffix}")
neg = graph.place("PreviewAny", col, [], title=f"Compiled negative{suffix}")
@@ -442,7 +441,7 @@ def workflow_zimage_01_quickstart() -> dict[str, Any]:
def workflow_zimage_02_consistency() -> dict[str, Any]:
"""One style across three subjects on Z-Image (plan P2-5)."""
"""One style across three subjects on Z-Image."""
g = Graph()
g.note(
0,
@@ -567,7 +566,7 @@ def workflow_01_quickstart() -> dict[str, Any]:
def workflow_02_consistency() -> dict[str, Any]:
"""
One style across three subjects, side by side (plan P2-5).
One style across three subjects, side by side.
Consistency is the claim this demonstrates, and it is scoped precisely: the
same style applied within this one model produces a coherent set. It is not
@@ -603,7 +602,7 @@ def workflow_02_consistency() -> dict[str, Any]:
def workflow_03_your_own_style() -> dict[str, Any]:
"""Your own style, made on the web, loaded here (plan P0-6's template 03)."""
"""Your own style, made on the web, loaded here (template 03)."""
g = Graph()
g.note(
0,
+3 -3
View File
@@ -9,7 +9,7 @@ Everything here is optional. If ComfyUI's server module isn't importable — a
headless test run, an API-only embed, a future frontend rewrite — importing this
fails, `__init__` catches it, and the nodes carry on with the plain text field.
The JS side probes /styleref/search once at startup and hides its button when
these routes are absent (plan P3-7), so both halves degrade together.
these routes are absent, so both halves degrade together.
"""
from __future__ import annotations
@@ -27,7 +27,7 @@ from styleref_nodes.load import bump_refresh
def _card(entry: dict) -> dict:
"""The picker-row shape. heroImage feeds the row thumbnail (plan P3-1)."""
"""The picker-row shape. heroImage feeds the row thumbnail."""
return {
"slug": entry.get("slug"),
"name": entry.get("name"),
@@ -143,7 +143,7 @@ async def styleref_my_styles(request: web.Request) -> web.Response:
}
for s in (payload.get("styles") or [])
],
# Passed back as ?cursor= for the dialog's Next button (plan P3-6).
# Passed back as ?cursor= for the dialog's Next button.
"nextCursor": payload.get("nextCursor"),
}
)
+3 -3
View File
@@ -418,6 +418,6 @@ def list_saved_styles(
# Note: this client deliberately has no extraction endpoints. Extraction is a
# web-app experience (preview, per-block editing, history, free re-apply) —
# plugin decision D1. The REST /extractions tier itself remains available for
# CLI and agent surfaces.
# web-app experience (preview, per-block editing, history, free re-apply), so
# the pack loads styles rather than making them. The REST /extractions tier
# itself remains available for CLI and agent surfaces.
+3 -3
View File
@@ -143,7 +143,7 @@ def is_headless(env: dict[str, str] | None = None) -> bool:
"""
True when we almost certainly cannot open a browser on this machine.
Plan risk 9.2: a large share of ComfyUI installs are remote GPU boxes where
a large share of ComfyUI installs are remote GPU boxes where
the loopback flow cannot work *at all* — localhost is the wrong machine.
Detecting that up front lets the Login node print the STYLEREF_TOKEN
instructions instead of hanging on a callback that will never arrive.
@@ -231,7 +231,7 @@ class _CallbackHandler(BaseHTTPRequestHandler):
code = (params.get("code") or [None])[0]
state = (params.get("state") or [None])[0]
# Validate BEFORE choosing the page (plan P0-4): the browser must never
# Validate BEFORE choosing the page: the browser must never
# claim success while the node reports a failed sign-in.
if not code or state != type(self).expected_state:
# A state mismatch is the CSRF case: discard the code entirely.
@@ -387,7 +387,7 @@ def bearer_token() -> str | None:
The token to send, or None for anonymous.
STYLEREF_TOKEN wins over stored credentials — it is the documented path for
headless installs (plan risk 9.2), so it must not be second-guessed.
headless installs, so it must not be second-guessed.
"""
env_token = (os.environ.get("STYLEREF_TOKEN") or "").strip()
if env_token:
+3 -3
View File
@@ -22,7 +22,7 @@ CATEGORY = "StyleRef"
# Node-facing target → the server's `format` parameter.
#
# The list is EXACTLY the web app's copy-box format list (owner decision
# 2026-07-22, superseding D2's midjourney removal), in the same order:
# 2026-07-22, superseding the earlier midjourney removal), in the same order:
# AI Tools, STYLE.md, Flux, Midjourney, Diffusion, JSON. The style means the
# same thing here as on styleref.io, format for format.
#
@@ -158,7 +158,7 @@ def estimate_tokens(text: str) -> int:
def preview_text(positive: str, target: str, compact: bool) -> str:
"""The node-body summary (P0-2/P1-8): prompt preview + token estimate."""
"""The node-body summary: prompt preview + token estimate."""
shown = positive if len(positive) <= PREVIEW_CHARS else positive[:PREVIEW_CHARS] + "…"
tokens = estimate_tokens(positive)
line = f"≈{tokens} tokens ({target})"
@@ -279,7 +279,7 @@ class StyleRefApply:
positive = compose_prompt(subject, style_text, subject_first)
negative = compose_negative(style, extra_negative)
# ui.text (P0-2): show what was actually composed, in the node body —
# ui.text: show what was actually composed, in the node body —
# otherwise the first visible evidence of the prompt is the final image.
return {
"ui": {"text": [preview_text(positive, target, compact)]},
+7 -8
View File
@@ -1,14 +1,14 @@
"""
StyleRef Load — fetch a style into the graph.
Widget policy (plan risk 9.1): every widget here is vanilla — a text field, a
Widget policy: every widget here is vanilla — a text field, a
combo, a toggle. The live search dialog is a *progressive enhancement* shipped
in web/styleref.js. When that JS fails to load, or a ComfyUI frontend release
breaks it, this node still works exactly as written: type or paste a ref. The
search path is additionally available headlessly via the `search` widget, whose
results land on the `search_results` output and in the node body.
Guidance policy (plan P1-2): Nodes 2.0 does not render STRING placeholders, so
Guidance policy: Nodes 2.0 does not render STRING placeholders, so
every input's contract must survive on tooltip + default alone. Placeholders are
kept (harmless where supported) but never as the only channel.
@@ -64,7 +64,7 @@ SEARCH_CATEGORIES = [
# ComfyUI re-runs a node whenever anything upstream changes, and re-fetching an
# unchanged style on every queue would burn the anonymous rate limit for no
# benefit. When the node does re-execute, a stored ETag turns the re-fetch into
# a cheap conditional GET (P5-8): the server answers 304 and the cached style —
# a cheap conditional GET: the server answers 304 and the cached style —
# including its compiled-prompt cache — is kept.
_CACHE: dict[str, dict[str, Any]] = {}
@@ -110,7 +110,7 @@ def fetch_style(ref: str, use_cache: bool = True) -> dict[str, Any]:
# With use_cache off (or after a refresh) we still send the stored ETag:
# "re-download every run" becomes "revalidate every run" — a bodyless 304
# when nothing changed, the full spec when it did (P5-8).
# when nothing changed, the full spec when it did.
etag = cached.get("etag") if cached else None
spec, canonical_url, new_etag = api.get_style_spec(ref, etag=etag)
if spec is None and cached is not None:
@@ -265,8 +265,7 @@ class StyleRefLoad:
summary = f"Loaded {summary_line(style)}"
# The search card carries no palette data, so the node body is where the
# palette shows up (plan P3-2's fallback) — as text, the only channel
# ui.text has.
# palette shows up — as text, the only channel ui.text has.
hexes = palette_hexes(style)[:5]
if hexes:
summary += f"\nPalette: {' '.join(hexes)}"
@@ -274,7 +273,7 @@ class StyleRefLoad:
if save_to_library:
summary += f"\n{self._save_to_library(style)}"
elif style.get("url"):
# Passive contribution nudge (plan P6-3): the style's public page is
# Passive contribution nudge: the style's public page is
# where renders made with it can be shared.
summary += f"\nMade something good with it? Share it on {style['url']}"
@@ -302,7 +301,7 @@ class StyleRefLoad:
@staticmethod
def _result(style: dict[str, Any], results: str, body: str):
# ui.text renders in the node body (P0-2): the load confirmation and any
# ui.text renders in the node body: the load confirmation and any
# search results are visible without wiring the STRING output anywhere.
return {"ui": {"text": [body]}, "result": (style, results)}
+1 -1
View File
@@ -14,7 +14,7 @@ without a frontend goes through `npx styleref login` (the CLI shares this
pack's credentials file) or STYLEREF_TOKEN; sign-out without a frontend is
deleting the credentials file, which the status text names.
Remote installs (plan risk 9.2): the loopback flow cannot work when ComfyUI
Remote installs: the loopback flow cannot work when ComfyUI
runs on a rented GPU box — there is no browser to open and `localhost` is the
wrong machine. The sign-in path detects that case up front and returns the
STYLEREF_TOKEN instructions instead of hanging on a callback that will never
+4 -4
View File
@@ -32,10 +32,10 @@ from typing import Any
PALETTE = ("colors", "color_palette")
MOOD = ("mood_personality", "mood_personality")
# Every guardrail field that belongs in an image model's NEGATIVE channel
# (plan P1-9 + the 2026-07-23 negation pass), in render order. avoided_words is
# deliberately absent: it constrains text output (buzzwords, brand voice) and
# stays in the text formats — the same split as voice_language.
# Every guardrail field that belongs in an image model's NEGATIVE channel, in
# render order. avoided_words is deliberately absent: it constrains text output
# (buzzwords, brand voice) and stays in the text formats — the same split as
# voice_language.
GUARDRAIL_ARTIFACTS = ("guardrails", "common_artifacts_to_avoid")
GUARDRAILS = ("guardrails", "avoided_visuals")
GUARDRAIL_COLORS = ("guardrails", "avoided_colors")
+4 -4
View File
@@ -160,7 +160,7 @@ def test_401_flags_needs_login(monkeypatch):
def test_401_with_env_token_hints_at_expiry(monkeypatch):
"""
Plan P0-5: the env token never refreshes itself, so a headless install's
The env token never refreshes itself, so a headless install's
401 hours later needs to say the likely cause — an expired STYLEREF_TOKEN.
"""
monkeypatch.setenv("STYLEREF_TOKEN", "stale-token")
@@ -213,7 +213,7 @@ def test_get_style_spec_reads_the_canonical_url_header(monkeypatch):
assert url == "https://styleref.io/share/real-slug"
# ── extraction stays out of this client (plan P0-6 / decision D1) ────────────
# ── extraction stays out of this client ──────────────────────────────────────
def test_client_has_no_extraction_endpoints():
@@ -306,7 +306,7 @@ def test_client_tag_header_is_sent(monkeypatch):
assert captured.get("X-styleref-client") == "comfyui"
# ── save (P5-6) ──────────────────────────────────────────────────────────────
# ── save ─────────────────────────────────────────────────────────────────────
def test_save_style_posts_to_the_save_endpoint(monkeypatch):
@@ -322,7 +322,7 @@ def test_save_style_posts_to_the_save_endpoint(monkeypatch):
assert seen["url"].endswith("/styles/%40ada%2FWarm%20Editorial/save")
# ── conditional GET (P5-8) ───────────────────────────────────────────────────
# ── conditional GET ──────────────────────────────────────────────────────────
def test_304_is_success_not_error(monkeypatch):
+2 -2
View File
@@ -138,7 +138,7 @@ def test_dead_session_degrades_to_anonymous(tmp_path, monkeypatch):
assert auth.bearer_token() is None
# ── headless detection (plan risk 9.2) ───────────────────────────────────────
# ── headless detection ───────────────────────────────────────────────────────
def test_ssh_session_is_headless():
@@ -174,7 +174,7 @@ def test_headless_help_leads_with_the_credentials_file_then_the_env_var():
assert help_text.index("credentials.json") < help_text.index("STYLEREF_TOKEN")
# ── OAuth callback page (plan P0-4) ──────────────────────────────────────────
# ── OAuth callback page ──────────────────────────────────────────────────────
class _FakeWFile:
+14 -14
View File
@@ -36,12 +36,12 @@ def clear_cache():
load_node._GENERATION.clear()
# ── pack registration (plan P0-6) ────────────────────────────────────────────
# ── pack registration ────────────────────────────────────────────────────────
def test_pack_registers_exactly_the_five_nodes():
"""
Extraction moved to the web app (decision D1): the pack registers
Extraction moved to the web app: the pack registers
Load/Apply/Facets/ReferenceImages/Login and nothing else — an Extract node
here means the removal regressed.
"""
@@ -65,7 +65,7 @@ def test_pack_registers_exactly_the_five_nodes():
def test_targets_are_exactly_the_web_apps_copy_list_in_order():
"""
Owner decision 2026-07-22 (supersedes D2): the target list mirrors the web
Owner decision 2026-07-22: the target list mirrors the web
app's copy-box format list exactly — same entries, same order — so the
style means the same thing here as on styleref.io. Source of truth:
PROMPT_FORMAT_OPTIONS in src/components/ui/PromptFormatDropdown.tsx.
@@ -246,7 +246,7 @@ def test_apply_rejects_an_unloaded_style():
node.apply(make_style(ref="", name="(none)"), "subject", "flux")
# ── Apply: node-body feedback (plan P0-2 / P1-8) ─────────────────────────────
# ── Apply: node-body feedback ────────────────────────────────────────────────
def test_apply_returns_ui_text_and_unchanged_result_tuple(monkeypatch):
@@ -370,7 +370,7 @@ def test_private_refs_are_tagged_as_library(monkeypatch):
def test_ungenerated_style_error_reaches_the_user_verbatim(monkeypatch):
"""
Plan P0-7: when the server refuses a never-generated own-style ref (409),
When the server refuses a never-generated own-style ref (409),
its message — which tells the user to Generate on styleref.io — must pass
through unreworded.
"""
@@ -390,7 +390,7 @@ def test_ungenerated_style_error_reaches_the_user_verbatim(monkeypatch):
def test_load_returns_ui_text_and_result_tuple(monkeypatch):
"""P0-2: the load confirmation renders in the node body."""
"""The load confirmation renders in the node body."""
monkeypatch.setattr(
api, "get_style_spec", lambda ref, etag=None: ({"name": "Warm", "sections": {}}, None, None)
)
@@ -422,7 +422,7 @@ def test_load_never_fetches_style_text(monkeypatch):
def test_load_is_an_output_node():
"""A search-only Load must be queueable alone and still display (P0-2)."""
"""A search-only Load must be queueable alone and still display."""
assert load_node.StyleRefLoad.OUTPUT_NODE is True
@@ -456,7 +456,7 @@ def test_search_failure_does_not_sink_the_run(monkeypatch):
def test_search_category_is_forwarded(monkeypatch):
"""The vanilla category combo (P3-4) must reach the API; `any` must not."""
"""The vanilla category combo must reach the API; `any` must not."""
seen = {}
def fake_search(query, category=None, sort=None, limit=12):
@@ -474,7 +474,7 @@ def test_search_category_is_forwarded(monkeypatch):
assert seen["category"] is None
# ── IS_CHANGED contracts (plan P0-1's regression class) ──────────────────────
# ── IS_CHANGED contracts ─────────────────────────────────────────────────────
def test_load_is_changed_is_stable_when_cached():
@@ -497,13 +497,13 @@ def test_login_is_always_dirty():
def test_apply_has_no_is_changed_override():
"""
Apply's dirtiness must come from its inputs alone. An IS_CHANGED override
returning NaN here is the credit-burn/api-hammer class of bug (plan P0-1) —
returning NaN here is the credit-burn/api-hammer class of bug —
the node would re-run on every queue.
"""
assert "IS_CHANGED" not in vars(apply_node.StyleRefApply)
# ── Apply: sections filter (P5-3) ────────────────────────────────────────────
# ── Apply: sections filter ───────────────────────────────────────────────────
def test_sections_filter_reaches_the_api_and_the_cache_key(monkeypatch):
@@ -527,7 +527,7 @@ def test_sections_filter_reaches_the_api_and_the_cache_key(monkeypatch):
assert calls[-1] is None
# ── Facets: full schema coverage (P5-2 / D4) ─────────────────────────────────
# ── Facets: full schema coverage ─────────────────────────────────────────────
def test_facets_outputs_are_exactly_the_schema_sections_plus_custom_items():
@@ -636,7 +636,7 @@ def test_reference_images_errors_clearly_without_images():
ri.StyleRefReferenceImages().fetch(make_style(ref="x", name="Bare"))
# ── Load: save_to_library (P5-6) ─────────────────────────────────────────────
# ── Load: save_to_library ────────────────────────────────────────────────────
def test_save_to_library_saves_once_per_session(monkeypatch):
@@ -690,7 +690,7 @@ def test_own_styles_are_not_re_saved(monkeypatch):
assert "Already your own style" in out["ui"]["text"][0]
# ── Load: ETag revalidation (P5-8) ───────────────────────────────────────────
# ── Load: ETag revalidation ──────────────────────────────────────────────────
def test_cache_off_revalidates_with_the_stored_etag(monkeypatch):
+2 -2
View File
@@ -128,7 +128,7 @@ def test_negatives_are_normalized_comma_terms(style):
def test_negatives_cover_artifacts_and_avoided_colors():
"""
Plan P1-9: guardrails carry more than avoided_visuals — artifacts to avoid
Guardrails carry more than avoided_visuals — artifacts to avoid
and avoided colors are user-authored constraints and belong in the negative.
"""
style = make_style(
@@ -241,7 +241,7 @@ def test_is_style_rejects_arbitrary_dicts():
assert is_style(make_style(ref="x", name="ok")) is True
# ── dimensions (P5-2: width/height from the style) ───────────────────────────
# ── dimensions (width/height from the style) ─────────────────────────────────
def _fmt_style(aspect: str | None = None, resolution: str | None = None):
+12 -12
View File
@@ -4,8 +4,8 @@ Workflow template tests.
A broken template is only discovered when a user drags it onto a canvas and it
fails silently, so the structural invariants are checked here instead: every
link points at a node that exists, at a slot that exists, carrying a type both
ends agree on — and the layout invariants of plan P2-1/P2-2: no overlapping
nodes, instructions in visible Note nodes, titles short enough to render.
ends agree on — plus the layout invariants: no overlapping nodes, instructions
in visible Note nodes, titles short enough to render.
"""
from __future__ import annotations
@@ -36,7 +36,7 @@ def _load(path: str) -> dict:
def _note_text(graph: dict) -> str:
"""All visible Note-node text — the instruction channel (plan P2-2)."""
"""All visible Note-node text — the instruction channel."""
return "\n".join(
str(n["widgets_values"][0])
for n in graph["nodes"]
@@ -50,7 +50,7 @@ def test_every_declared_workflow_is_committed():
def test_extraction_template_is_gone():
"""Plan P0-6: extraction lives on the web; template 03 tells that story."""
"""Extraction lives on the web; template 03 tells that story."""
assert "03-extract-and-reuse.json" not in {os.path.basename(p) for p in FILES}
assert "03-your-own-style.json" in {os.path.basename(p) for p in FILES}
@@ -78,7 +78,7 @@ def test_committed_file_matches_the_generator(path):
@pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES])
def test_only_the_shipped_styleref_nodes_appear(path):
"""Plan P0-6: a template referencing a removed node fails on the canvas."""
"""A template referencing a removed node fails on the canvas."""
allowed = {
"StyleRefLoad",
"StyleRefApply",
@@ -128,7 +128,7 @@ def test_link_ids_are_unique_and_registered_on_both_ends(path):
@pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES])
def test_no_nodes_overlap_at_default_sizes(path):
"""Plan P2-1: overlapping nodes read as a broken template — layout is math,
"""Overlapping nodes read as a broken template — layout is math,
not guesses, so this must hold for every template."""
nodes = _load(path)["nodes"]
boxes = [(n["id"], *n["pos"], *n["size"]) for n in nodes]
@@ -142,7 +142,7 @@ def test_no_nodes_overlap_at_default_sizes(path):
def test_every_workflow_explains_itself_in_a_visible_note(path):
"""
Templates are documentation — and ComfyUI never renders extra.styleref, so
the instructions must live in a visible core Note node (plan P2-2).
the instructions must live in a visible core Note node.
"""
graph = _load(path)
assert len(_note_text(graph)) > 60, "no substantial Note node found"
@@ -168,14 +168,14 @@ def test_auth_requirements_are_stated(path):
needs_auth = any(n["type"] == "StyleRefLogin" for n in graph["nodes"])
if needs_auth:
assert "sign-in" in notes or "sign in" in notes
# The sign-in path ships in the template (plan P2-6), not just in prose.
# The sign-in path ships in the template, not just in prose.
assert any(n["type"] == "StyleRefLogin" for n in graph["nodes"])
@pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES])
def test_seeds_randomize(path):
"""
Plan P2-4: a fixed seed makes the second queue a silent cache no-op —
A fixed seed makes the second queue a silent cache no-op —
deadly in a demo.
"""
for node in _load(path)["nodes"]:
@@ -190,7 +190,7 @@ _MODEL_DIRECTORIES = {"checkpoints", "diffusion_models", "text_encoders", "vae"}
@pytest.mark.parametrize("path", FILES, ids=[_relpath(p) for p in FILES])
def test_model_loaders_carry_download_metadata(path):
"""Plan P2-7: a missing model should be a guided download, not a red error —
"""A missing model should be a guided download, not a red error —
for every loader type (checkpoints and the Z-Image UNET/CLIP/VAE stack)."""
graph = _load(path)
loader_files = {
@@ -288,7 +288,7 @@ def test_zimage_templates_have_no_negative_encoder(name):
def test_consistency_template_shows_one_style_many_subjects():
"""Plan P2-5: three Apply nodes with three different subjects, one Load."""
"""Three Apply nodes with three different subjects, one Load."""
graph = _load(os.path.join(WORKFLOW_DIR, "02-consistency-grid.json"))
applies = [n for n in graph["nodes"] if n["type"] == "StyleRefApply"]
loads = [n for n in graph["nodes"] if n["type"] == "StyleRefLoad"]
@@ -299,7 +299,7 @@ def test_consistency_template_shows_one_style_many_subjects():
def test_prompt_previews_exist_where_promised():
"""Plan P2-3: templates 01 and 04 make the compiled prompts visible."""
"""Templates 01 and 04 make the compiled prompts visible."""
for name in ("01-quickstart.json", "04-facets.json"):
graph = _load(os.path.join(WORKFLOW_DIR, name))
assert any(n["type"] == "PreviewAny" for n in graph["nodes"]), name
+9 -9
View File
@@ -53,7 +53,7 @@ const CATEGORIES = [
// ‹ Prev / Next ›, so the dialog stays a fixed height instead of growing.
const PAGE_SIZE = 8;
// Resolved by the availability probe (plan P3-7). `null` = probe still running.
// Resolved by the availability probe. `null` = probe still running.
let routesAvailable = null;
async function probeRoutes() {
@@ -101,7 +101,7 @@ function el(tag, css, text) {
}
/**
* The picker dialog (plan P3-3/P3-5): owns its query, filters, and result
* The picker dialog: owns its query, filters, and result
* list. Resolves with the chosen slug/ref, or null when dismissed.
*/
function openPicker(initialQuery) {
@@ -124,7 +124,7 @@ function openPicker(initialQuery) {
resolve(value);
};
const onKey = (e) => {
// Esc-to-close (plan P3-5), capturing so the canvas never sees it.
// Esc-to-close, capturing so the canvas never sees it.
if (e.key === "Escape") {
e.stopPropagation();
close(null);
@@ -213,7 +213,7 @@ function openPicker(initialQuery) {
button.onmouseenter = () => (button.style.background = "#32323a");
button.onmouseleave = () => (button.style.background = "#26262a");
// Thumbnail (plan P3-1) — a fixed box so rows align with or without one.
// Thumbnail — a fixed box so rows align with or without one.
const thumb = el(
"div",
"width:44px;height:44px;border-radius:6px;background:#1a1a1d;flex:none;overflow:hidden;",
@@ -410,7 +410,7 @@ function openPicker(initialQuery) {
return;
}
if (gallery.error) {
// Inline message row, never alert() (plan P3-5).
// Inline message row, never alert().
out.append(message(gallery.error));
return;
}
@@ -593,7 +593,7 @@ async function refreshLoginStatus(node, statusWidget) {
}
}
/** True when Apply's `positive` output feeds a CLIPTextEncode (plan P1-3). */
/** True when Apply's `positive` output feeds a CLIPTextEncode. */
function positiveFeedsClipEncode(node) {
const graph = node.graph;
const output = node.outputs?.find((o) => o.name === "positive");
@@ -619,7 +619,7 @@ app.registerExtension({
const result = onCreated?.apply(this, arguments);
// Only add the search button when the backend routes exist
// (plan P3-7) — otherwise it could only 404.
// — otherwise it could only 404.
if (routesAvailable !== false) {
this.addWidget("button", "Search styles…", null, async () => {
const refWidget = this.widgets?.find((w) => w.name === "style_ref");
@@ -635,7 +635,7 @@ app.registerExtension({
});
}
// Refresh affordance (plan P1-6): the backend drops its cached
// Refresh affordance: the backend drops its cached
// copy and dirties the node, so the next queue re-downloads —
// no need to toggle use_cache off and back on.
if (routesAvailable !== false) {
@@ -662,7 +662,7 @@ app.registerExtension({
}
if (nodeData?.name === APPLY_NODE) {
// Warning badge (plan P1-3): `natural`/`style_md` are copy-out
// Warning badge: `natural`/`style_md` are copy-out
// documents; wiring one into a CLIP encoder is almost certainly a
// mistake. Additive drawing only — nothing else is touched.
const onDrawForeground = nodeType.prototype.onDrawForeground;