"""Unit: the built-in identity palette + the effective-settings resolver
(phase 91, task 01).
Covers ``app/core/theming.py`` — the single source of the built-in
identity palette (re-homed from the retired phase-62 CSS-file themes'
authoring guide before task 03 deleted it) and the DB-over-env /
DB-over-built-in resolver shared by ``/api/ui-settings`` and
``/api/config``:
* ``BUILTIN_COLORS`` — the DRIFT GUARD: the 17 built-ins (9 identity +
8 semantic state, phase 93) must equal the values parsed straight
out of ``frontend/assets/styles.css``'s ``:root`` block, so the
Python palette and the stylesheet can never silently diverge;
* ``theme_style_tag`` — the byte-identical contract (all built-in →
``""``) and the exact tag shape (all 17 variables, ``COLOR_FIELDS``
order, lowercased hex);
* ``effective_settings`` — missing row → env strings + built-ins; a DB
row's set columns win; an empty-string DB string falls back to env
(the resolver treats "" as unset, B1). House DB-test pattern (the
``test_tokens`` precedent): the real compose Postgres, skipped with
clear instructions when the stack is not up.
* Phase 156 (task 03): the catalogue-table CSS contract pins, parsed
straight out of ``styles.css`` (the :root-drift idiom extended — a
later "cleanup" cannot silently unstick the header, unbound the
panel, or transparent the thead): the bounded ``.table-wrap``
panel, the sticky opaque ``thead th``, the
``#git-sources-table-wrap`` one-rule harmony, the
``.md-table-wrap`` byte-unchanged scope guard, the ``.cell-clip``
ellipsis. The :root 17-variable set itself stays pinned by
``test_builtin_colors_match_styles_css_root`` (UNCHANGED — this
phase adds no palette variable, B3).
"""
from __future__ import annotations
import base64
import hashlib
import re
from pathlib import Path
from typing import Any
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.config import Settings
from app.core import theming
from app.models import UiSettings
REPO_ROOT = Path(__file__).resolve().parents[2]
STYLES_CSS = REPO_ROOT / "frontend" / "assets" / "styles.css"
def _delete_row() -> Any:
from sqlalchemy import delete
return delete(UiSettings).where(UiSettings.id == 1)
def _start_row_missing(db: Session) -> None:
"""The single row is global state: wipe it so every DB test starts
from the row-missing state it asserts (a stale row from an earlier
interrupted run must not break them)."""
db.execute(_delete_row())
db.commit()
def _root_declarations() -> dict[str, str]:
"""The ``--name: value`` declarations of styles.css's (first)
``:root`` block, comments stripped, in file order."""
css = STYLES_CSS.read_text(encoding="utf-8")
match = re.search(r":root\s*\{", css)
assert match is not None, "styles.css must have a :root block"
block = css[match.end() : css.index("}", match.end())]
block = re.sub(r"/\*.*?\*/", "", block, flags=re.S)
return dict(re.findall(r"(--[a-z-]+)\s*:\s*([^;]+);", block))
def test_builtin_colors_match_styles_css_root() -> None:
"""The drift guard: every built-in equals the stylesheet's ``:root``
value for the same variable (and ``BUILTIN_COLORS`` names exactly
the 17 palette variables — the 9 identity + the 8 semantic state,
no more, no fewer; phase 93, task 01)."""
decls = _root_declarations()
builtin_names = set(theming.BUILTIN_COLORS)
assert builtin_names == {
"bg", "surface", "ink", "ink_soft", "line", "grid_line",
"brand", "brand_soft", "brand_ink",
"ok_bg", "ok_ink", "err_bg", "err_ink", "err_line",
"accent_bg", "accent_ink", "accent_line",
}, f"BUILTIN_COLORS must name exactly the 17 palette variables, got {sorted(builtin_names)}"
for name, value in theming.BUILTIN_COLORS.items():
css_name = f"--{name.replace('_', '-')}"
assert css_name in decls, f"styles.css :root is missing {css_name}"
assert decls[css_name].strip() == value, (
f"{css_name} drifted: BUILTIN_COLORS has {value!r}, "
f"styles.css has {decls[css_name].strip()!r}"
)
def test_color_fields_are_the_17_keys_in_order() -> None:
"""``COLOR_FIELDS`` is the 17 keys — the 9 identity in the
themes-README order (phase 92: ``grid_line`` between ``line`` and
``brand``), then the 8 semantic state variables (phase 93: ok,
err, accent — identity, brand, then state) — the order the
resolver, the API, and the tag renderer all rely on."""
assert theming.COLOR_FIELDS == (
"bg", "surface", "ink", "ink_soft",
"line", "grid_line", "brand", "brand_soft", "brand_ink",
"ok_bg", "ok_ink", "err_bg", "err_ink", "err_line",
"accent_bg", "accent_ink", "accent_line",
)
assert len(theming.COLOR_FIELDS) == 17
assert theming.STRING_FIELDS == ("app_name", "input_placeholder", "footer_text")
def _env_settings() -> Settings:
"""An explicit env source (the resolver's optional ``settings``
parameter) — deterministic values, independent of the local ``.env``
(the ``/api/config`` env pins in test_api.py own the env-file
behaviour; this unit module only needs stable fallbacks)."""
return Settings(
app_name="Env Name",
input_placeholder="Env placeholder…",
footer_text="Env footer",
)
# ---------- effective_settings (real Postgres — house DB-test pattern) ----------
def test_effective_missing_row_is_env_strings_plus_builtins(db: Session) -> None:
"""A missing row (GET creates nothing) means "defaults": the env
strings + the built-in palette, all 20 values (3 strings + 17
colors — phase 93, task 01)."""
_start_row_missing(db)
row = db.execute(select(UiSettings).where(UiSettings.id == 1)).scalars().first()
assert row is None, "the test starts from a row-missing state"
effective = theming.effective_settings(db, _env_settings())
assert set(effective) == set(theming.STRING_FIELDS) | set(theming.COLOR_FIELDS)
assert len(effective) == 20 # 3 strings + 17 colors (9 identity + 8 semantic)
assert effective["app_name"] == "Env Name"
assert effective["input_placeholder"] == "Env placeholder…"
assert effective["footer_text"] == "Env footer"
assert {k: effective[k] for k in theming.COLOR_FIELDS} == theming.BUILTIN_COLORS
def test_effective_db_row_wins_column_by_column(db: Session) -> None:
"""Set columns win, unset columns fall back — per column, so a
partial row (only ``bg`` set) mixes the DB color with the built-ins
and the env strings."""
_start_row_missing(db)
db.add(UiSettings(id=1, bg="#111111", app_name="DB Name"))
db.commit()
try:
effective = theming.effective_settings(db, _env_settings())
assert effective["bg"] == "#111111" # DB wins
assert effective["app_name"] == "DB Name" # DB wins
# Unset columns: env strings + the built-in colors.
assert effective["input_placeholder"] == "Env placeholder…"
assert effective["footer_text"] == "Env footer"
for key in theming.COLOR_FIELDS:
if key != "bg":
assert effective[key] == theming.BUILTIN_COLORS[key]
finally:
db.execute(_delete_row())
db.commit()
def test_effective_empty_string_db_string_falls_back_to_env(db: Session) -> None:
"""B1: an EMPTY string in the DB is unset — the resolver falls back
to the env value (a hand-edited row with '' can't blank the UI).
Colors: ``None`` → the built-in (an empty color is impossible through
the API — the hex validator — the resolver's not-None rule covers
the hand-edited edge by returning whatever the row holds)."""
_start_row_missing(db)
db.add(UiSettings(id=1, app_name=""))
db.commit()
try:
effective = theming.effective_settings(db, _env_settings())
assert effective["app_name"] == "Env Name" # "" → env fallback
assert effective["footer_text"] == "Env footer"
assert effective["brand"] == theming.BUILTIN_COLORS["brand"] # None → built-in
finally:
db.execute(_delete_row())
db.commit()
def test_effective_without_explicit_settings_uses_get_settings(db: Session) -> None:
"""``settings=None`` (the design's call shape) resolves the env
fallback from the cached :func:`app.config.get_settings` — the
values it reports must be real ``str``s for all 20 values."""
from app.config import get_settings
_start_row_missing(db)
effective = theming.effective_settings(db)
assert set(effective) == set(theming.STRING_FIELDS) | set(theming.COLOR_FIELDS)
assert effective["app_name"] == get_settings().app_name
for field in theming.STRING_FIELDS:
assert isinstance(effective[field], str) and effective[field]
assert all(re.fullmatch(r"#[0-9a-f]{6}", effective[k]) for k in theming.COLOR_FIELDS)
assert {k: effective[k] for k in theming.COLOR_FIELDS} == theming.BUILTIN_COLORS
# ---------- theme_style_tag (pure) ----------
def test_theme_style_tag_all_builtins_is_empty_string() -> None:
"""The byte-identical contract: an unset (or "defaults saved")
deployment serves NO tag — exactly the pre-phase-91 HTML."""
assert theming.theme_style_tag(dict(theming.BUILTIN_COLORS)) == ""
# The tag is PURE string-equality: uppercase hex is NOT the built-in
# (the API's lowercasing-before-store is what makes stored hex
# canonical — pin the renderer's own contract here).
colors = {k: v.upper() for k, v in theming.BUILTIN_COLORS.items()}
assert theming.theme_style_tag(colors) != ""
def test_theme_style_tag_one_changed_semantic_carries_all_17_in_order() -> None:
"""A single NON-BUILT-IN SEMANTIC variable (phase 93, task 01) still
emits ALL 17 declarations, in ``COLOR_FIELDS`` order, with the exact
tag shape (no whitespace): the 9 identity variables keep their
built-ins, the 8 semantic variables carry ``--ok-ink:#444444;`` (the
change) plus the 7 other semantic built-ins — and the CSP hash
matches the tag's content (the runtime exemption contract)."""
colors = dict(theming.BUILTIN_COLORS)
colors["ok_ink"] = "#444444" # one non-default SEMANTIC var
tag = theming.theme_style_tag(colors)
assert tag == (
'"
)
# The changed value lands under the dashed CSS name…
assert "--ok-ink:#444444;" in tag
# …and the underscored field (ink_soft) renders as --ink-soft.
assert "--ink-soft:#b8a8a8;" in tag
assert "--ink_soft" not in tag
# Exactly 17 declarations, COLOR_FIELDS order.
names = re.findall(r"--([a-z-]+):", tag)
assert names == [k.replace("_", "-") for k in theming.COLOR_FIELDS]
# The hash is computed from the tag's EXACT content (CSP3 §13.4).
content = tag.split(">", 1)[1].rsplit("", 1)[0]
expected = "sha256-" + base64.b64encode(
hashlib.sha256(content.encode("utf-8")).digest()
).decode("ascii")
assert theming.theme_csp_hash(tag) == expected
def test_theme_style_tag_multiple_changed() -> None:
"""Two changed colors: both values present, the rest built-in, order
unchanged (the tag is a complete :root override — the page never
mixes a partial palette)."""
colors = dict(theming.BUILTIN_COLORS)
colors["bg"] = "#0a0e1a"
colors["brand_ink"] = "#c7d2fe"
colors["accent_ink"] = "#cccccc" # a semantic var joins the mix too
tag = theming.theme_style_tag(colors)
assert tag.startswith('")
# The order of the 17 dashed names is the COLOR_FIELDS order.
names = re.findall(r"--([a-z-]+):", tag)
assert names == [k.replace("_", "-") for k in theming.COLOR_FIELDS]
# ---------- inject_theme (pure — task 02's injection helper) ----------
_HEAD_HTML = "
tb
"
def test_inject_theme_empty_tag_is_identity() -> None:
"""``tag == ""`` (the unset / "defaults saved" deployment — what
``theme_style_tag" returns for all-built-in colors) → the html is
returned EXACTLY as passed in, byte for byte (B4's no-op
contract)."""
assert theming.inject_theme(_HEAD_HTML, "") == _HEAD_HTML
# Whitespace is NOT an empty tag — a real tag is always inserted.
assert theming.inject_theme(_HEAD_HTML, " ") != _HEAD_HTML
def test_inject_theme_missing_head_is_identity() -> None:
"""No ```` occurrence → unchanged (nothing to anchor to);
the empty string (no ```` either) is identity too."""
tag = ''
html = "no head"
assert theming.inject_theme(html, tag) == html
assert theming.inject_theme("", tag) == ""
def test_inject_theme_exact_placement_before_first_head_close() -> None:
"""The tag lands with a leading newline immediately BEFORE the
first ```` — nothing between the tag and the close, nothing
moved after it."""
tag = ''
html = "tafter"
assert theming.inject_theme(html, tag) == (
"t\n" + tag + "after"
)
# A LATER ````-shaped stretch of text is not the anchor — the
# FIRST occurrence wins (the one that closes the real head).
html2 = ""
assert theming.inject_theme(html2, tag) == (
"\n" + tag + ""
)
def test_inject_theme_double_injection_is_idempotent() -> None:
"""The defensive idempotence rule keys on the id: once a
``id="bor-theme"`` tag is present the helper is the identity — the
page can never carry two theme tags, even for a different tag."""
tag = ''
once = theming.inject_theme(_HEAD_HTML, tag)
assert once.count('id="bor-theme"') == 1
assert theming.inject_theme(once, tag) == once
other = ''
assert theming.inject_theme(once, other) == once
# ---------------------------------------------------------------------------
# Phase 91 (task 05, defect fix): theme_csp_hash — the CSP3 hash of the
# inline tag's content (the phase-82 CSP would otherwise BLOCK the tag
# in every real browser; the caching middleware publishes the hash as a
# style-src exemption on themed HTML pages only).
# ---------------------------------------------------------------------------
def test_theme_csp_hash_empty_tag_is_empty_string() -> None:
"""No tag (unset/defaults deployment) → no hash — the plain A1
policy stands and the header stays byte-identical to pre-91."""
assert theming.theme_csp_hash("") == ""
def test_theme_csp_hash_is_sha256_of_the_tag_content() -> None:
"""CSP3 §13.4: the hash covers the character data BETWEEN the tags
(the ``:root{…}`` declarations — the rendered content carries no
leading/trailing whitespace, so no stripping applies), base64
after SHA-256, ``sha256-`` prefixed."""
tag = ''
expected = "sha256-" + base64.b64encode(
hashlib.sha256(b":root{--bg:#111111}").digest()
).decode("ascii")
assert theming.theme_csp_hash(tag) == expected
def test_theme_csp_hash_changes_with_the_palette() -> None:
"""A different palette → a different hash: the browser keeps
blocking the OLD tag once the theme changes (the exemption always
matches exactly the served bytes, never a stale palette)."""
colors = dict(theming.BUILTIN_COLORS)
colors["brand"] = "#818cf8"
first = theming.theme_csp_hash(theming.theme_style_tag(colors))
colors["brand"] = "#22c55e"
second = theming.theme_csp_hash(theming.theme_style_tag(colors))
assert first
assert first != second
assert first.startswith("sha256-")
assert second.startswith("sha256-")
# ---------------------------------------------------------------------------
# Phase 93 (task 02): the docstring ↔ theme.js PAIRS mirror — the
# authoritative eight-pair table and the client-side warning list must
# NEVER diverge (the docstring names the mirror; this test pins it).
# ---------------------------------------------------------------------------
def _theme_js_pairs() -> list[tuple[str, str]]:
"""The (foreground, background) entries of ``theme.js``'s ``PAIRS``
array, in order (frontend source read as text — the house
pattern)."""
js = (REPO_ROOT / "frontend" / "assets" / "theme.js").read_text(encoding="utf-8")
start = js.index("const PAIRS = [")
end = js.index("];", start)
return re.findall(r'\[\s*"([a-z_]+)"\s*,\s*"([a-z_]+)"\s*\]', js[start:end])
def test_docstring_pair_table_matches_theme_js_pairs() -> None:
"""The mirror contract: ``theme.js``'s ``PAIRS`` is exactly the
EIGHT pairs the module docstring's authoritative table names —
every PAIRS entry appears in the docstring as ``fg`` on ``bg``
(and the list has exactly eight entries, so a pair silently added
to ONE side fails)."""
# Line-wrap-tolerant: the docstring table wraps at 79 columns
# (``ink_soft``\non ``surface``), so newlines become spaces.
doc = (theming.__doc__ or "").replace("\n", " ")
pairs = _theme_js_pairs()
assert len(pairs) == 8, f"PAIRS must hold exactly 8 pairs, got {pairs}"
for fg, bg in pairs:
assert f"``{fg}`` on ``{bg}``" in doc, (
f"the docstring's authoritative pair table must name "
f"``{fg}`` on ``{bg}`` (the theme.js mirror)"
)
# The two *_line state variables stay EXCLUDED from the warning
# surface in BOTH places (decorative borders — no contrast duty).
assert "err_line" not in [f for f, _ in pairs]
assert "accent_line" not in [f for f, _ in pairs]
# ---------------------------------------------------------------------------
# Phase 156 (task 03): the catalogue-table CSS contract pins — the
# bounded-panel sticky header + the clipped-label ellipsis, parsed
# straight out of styles.css (the :root-drift idiom extended: a later
# "cleanup" cannot silently unstick the header, unbound the panel, or
# transparent the thead). The :root 17-variable set itself is pinned
# by test_builtin_colors_match_styles_css_root above (UNCHANGED —
# this phase adds no palette variable, B3: the 17-variable set stays
# byte-identical).
# ---------------------------------------------------------------------------
def _css_rule(selector: str) -> dict[str, str] | None:
"""The ``prop: value`` declarations (comments stripped) of the FIRST
styles.css rule whose selector ends with ``selector`` (a bare
``.table-wrap`` never matches ``.table-wrap table thead th`` —
the selector must end right at the rule's ``{``), or ``None``
when the selector is absent from the file."""
css = re.sub(r"/\*.*?\*/", "", STYLES_CSS.read_text(encoding="utf-8"), flags=re.S)
match = re.search(re.escape(selector) + r"(?![\w.#-])\s*\{", css)
if match is None:
return None
block = css[match.end() : css.index("}", match.end())]
return dict(re.findall(r"([a-z-]+)\s*:\s*([^;]+);", block))
def _assert_opaque_background(value: str, root: dict[str, str]) -> None:
"""The sticky th's OPACITY pin (the exact token is the phase's
choice — ``var(--brand-soft)`` today; the opacity is the pin):
the background token must paint a solid, alpha-less color —
``transparent``/``none`` never, a ``var(--x)`` token must resolve
to a solid six-hex ``:root`` value, a literal must carry no
alpha channel (``rgba(`` / ``hsla(`` / 8-digit hex all let rows
show through the pinned header)."""
v = value.strip()
assert v.lower() not in {"transparent", "none", "inherit", "unset", "initial"}, (
f"the sticky th background is {value!r} — rows would show through"
)
var = re.fullmatch(r"var\(\s*(--[a-z-]+)\s*\)", v)
if var:
name = var.group(1)
assert name in root, f"the sticky th background {value!r} names an unknown {name}"
resolved = root[name]
assert re.fullmatch(r"#[0-9a-f]{6}", resolved), (
f"the sticky th background {value!r} resolves to {resolved!r} — it must "
f"be a solid alpha-less hex (OPAQUE)"
)
return
assert not re.search(r"rgba?\(\s|hsla?\(\s|#[0-9a-f]{8}\b", v), (
f"the sticky th background {value!r} carries an alpha channel — "
f"it must be OPAQUE"
)
def test_table_wrap_bounded_panel_pin() -> None:
"""The ONE rule all three catalogue tables share (the RAG
``.docs-table``, the Sources ``.git-sources-table``, the History
``.history-table``): the card is the vertical scroll region —
``max-height: min(70vh, 64rem)`` (the exact A2 value pinned: a
15-row table scrolls while a 3-row one fits without a scrollbar)
+ ``overflow: auto`` (both axes — the tables' min-width still
triggers the in-card horizontal scroll at narrow widths). The
pre-phase ``overflow-x: auto`` is SUPERSEDED — its return would
unbound the panel's vertical contract (a sticky header in an
unbounded card scrolls away with the page)."""
rule = _css_rule(".table-wrap")
assert rule is not None, "styles.css must define .table-wrap"
assert rule.get("max-height") == "min(70vh, 64rem)", (
f".table-wrap max-height drifted: {rule.get('max-height')!r} — the "
f"bounded-panel pin is min(70vh, 64rem)"
)
assert rule.get("overflow") == "auto", (
f".table-wrap overflow drifted: {rule.get('overflow')!r} — the card "
f"must be the sticky th's scrolling ancestor (overflow: auto)"
)
assert "overflow-x" not in rule, (
".table-wrap must not carry a bare overflow-x — the pre-phase "
"overflow-x: auto is superseded by overflow: auto (both axes)"
)
def test_table_wrap_thead_sticky_pin() -> None:
"""The ONE sticky-header rule for every catalogue table:
``position: sticky; top: 0; z-index: 1`` (the header row pinned
to the card top while the card's own vertical scroll moves, the
pinned th above the scrolled cells) + an OPAQUE background — the
parse asserts the background is a non-``transparent`` token (the
exact token is the phase's choice — ``var(--brand-soft)`` today;
the OPACITY is the pin: a ``var`` token must resolve to a solid
alpha-less :root hex, a literal must carry no alpha channel —
rows must never show through the pinned header)."""
rule = _css_rule(".table-wrap table thead th")
assert rule is not None, "styles.css must define .table-wrap table thead th"
assert rule.get("position") == "sticky", (
f"the catalogue thead th must stay position: sticky, "
f"got {rule.get('position')!r}"
)
assert rule.get("top") == "0", (
f"the catalogue thead th must pin at top: 0, got {rule.get('top')!r}"
)
assert rule.get("z-index") == "1", (
f"the catalogue thead th must sit at z-index: 1, got {rule.get('z-index')!r}"
)
assert "background" in rule, (
"the catalogue thead th must carry an explicit background "
"(the opaque pin — the default is transparent)"
)
_assert_opaque_background(rule["background"], _root_declarations())
def test_git_sources_table_wrap_no_divergent_override() -> None:
"""The one-rule contract: the Sources card
(``#git-sources-table-wrap``) must not carry a DIVERGENT UNBOUNDED
override — if the id rule exists it carries the SAME
``max-height: min(70vh, 64rem)`` + ``overflow: auto`` as the base
``.table-wrap`` (a divergent ``overflow-x: auto`` without the
bound would make the Sources table scroll away with the page
while the other two stick). The current form (task 01): the id
rule is REMOVED — the wrapper carries ``class="table-wrap"`` and
inherits the base card's bound + overflow + sticky rule, so the
rule's absence IS the harmony."""
rule = _css_rule("#git-sources-table-wrap")
if rule is None:
return # the one-rule contract: the id carries no rule of its own
assert rule.get("max-height") == "min(70vh, 64rem)", (
f"#git-sources-table-wrap max-height diverges: "
f"{rule.get('max-height')!r} — the one-rule contract pins min(70vh, 64rem)"
)
assert rule.get("overflow") == "auto", (
f"#git-sources-table-wrap overflow diverges: {rule.get('overflow')!r} — "
f"the one-rule contract pins overflow: auto"
)
def test_md_table_wrap_byte_unchanged_pin() -> None:
"""The scope guard: the CHAT view's markdown table wrapper
(``.md-table-wrap`` — the shared renderer's horizontal scroller)
is a DIFFERENT class from the catalogue card — UNTOUCHED by this
phase (no card, no height bound; the bounded-panel design is for
the catalogue tables only). The pin asserts the rule's VERBATIM
pre-phase text (byte-identical)."""
css = STYLES_CSS.read_text(encoding="utf-8")
assert ".md-table-wrap { overflow-x: auto; }" in css, (
".md-table-wrap must stay byte-identical to its pre-phase rule "
"(.md-table-wrap { overflow-x: auto; }) — the chat scope guard"
)
def test_cell_clip_ellipsis_pin() -> None:
"""The clipped-label ellipsis (A3): the ``.cell-clip`` label span
(the five control families — the Sources Hidden checkbox label +
Ignore paths + Remove, the RAG folder table's Edit, the History
Copy / Unshare / Create link / Delete) shows "…" instead of the
card edge's hard cut: ``text-overflow: ellipsis`` +
``overflow: hidden`` + ``white-space: nowrap`` (the house
.sync-label shrinkable-item mechanism; the max-width clamp
engages only in the clipped case)."""
rule = _css_rule(".cell-clip")
assert rule is not None, "styles.css must define .cell-clip"
assert rule.get("text-overflow") == "ellipsis", (
f".cell-clip text-overflow drifted: {rule.get('text-overflow')!r} — "
f"the clipped label must show the ellipsis hint"
)
assert rule.get("overflow") == "hidden", (
f".cell-clip overflow drifted: {rule.get('overflow')!r} — "
f"the ellipsis needs the hidden overflow"
)
assert rule.get("white-space") == "nowrap", (
f".cell-clip white-space drifted: {rule.get('white-space')!r} — "
f"the ellipsis needs the nowrap"
)