render: colourise diff details + widen card, no mid-line wrap
When details is a unified diff (the network-configs notify feeds `git diff HEAD~1`), colour each line like a pager: additions green, deletions red, hunk headers violet, file/meta headers dim. Non-diff details (auto-log output) still render as a plain muted block. Also widen the card 600->760px and switch the details <pre> from pre-wrap/word-break to white-space:pre + overflow-x:auto so diff lines scroll instead of wrapping into mush. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+69
-4
@@ -168,6 +168,71 @@ def fetch_failing_log(
|
|||||||
return "\n".join(cleaned[-log_lines:]).strip()
|
return "\n".join(cleaned[-log_lines:]).strip()
|
||||||
|
|
||||||
|
|
||||||
|
# --- diff rendering -------------------------------------------------------
|
||||||
|
# When `details` is a unified diff (the network-configs notify feeds
|
||||||
|
# `git diff HEAD~1`), colour each line the way a terminal pager would:
|
||||||
|
# additions green, deletions red, hunk headers violet, file/meta headers dim.
|
||||||
|
# Non-diff details (e.g. auto-log output) fall through to a single muted block.
|
||||||
|
DIFF_ADD = OK_FG
|
||||||
|
DIFF_DEL = ERROR_FG
|
||||||
|
DIFF_HUNK = ACCENT_VIOLET
|
||||||
|
DIFF_META = MUTED
|
||||||
|
DIFF_CONTEXT = MUTED_STRONG
|
||||||
|
|
||||||
|
_HUNK_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@")
|
||||||
|
# Lines that start with +/- but are file headers, not content changes.
|
||||||
|
_META_PREFIXES = (
|
||||||
|
"diff --git ",
|
||||||
|
"index ",
|
||||||
|
"--- ",
|
||||||
|
"+++ ",
|
||||||
|
"new file mode ",
|
||||||
|
"deleted file mode ",
|
||||||
|
"old mode ",
|
||||||
|
"new mode ",
|
||||||
|
"rename ",
|
||||||
|
"similarity index ",
|
||||||
|
"copy ",
|
||||||
|
"Binary files ",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _looks_like_diff(text: str) -> bool:
|
||||||
|
"""True if `text` reads as a unified diff (has a git header or a hunk)."""
|
||||||
|
for ln in text.splitlines():
|
||||||
|
if ln.startswith("diff --git ") or _HUNK_RE.match(ln):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _diff_line_color(line: str) -> str:
|
||||||
|
if _HUNK_RE.match(line):
|
||||||
|
return DIFF_HUNK
|
||||||
|
if line.startswith(_META_PREFIXES):
|
||||||
|
return DIFF_META
|
||||||
|
if line.startswith("+"):
|
||||||
|
return DIFF_ADD
|
||||||
|
if line.startswith("-"):
|
||||||
|
return DIFF_DEL
|
||||||
|
return DIFF_CONTEXT
|
||||||
|
|
||||||
|
|
||||||
|
def colorize_diff_html(details: str) -> str:
|
||||||
|
"""Return inner HTML for the details <pre>: one coloured span per line."""
|
||||||
|
spans = [
|
||||||
|
f'<span style="color:{_diff_line_color(line)};">{escape(line)}</span>'
|
||||||
|
for line in details.split("\n")
|
||||||
|
]
|
||||||
|
return "\n".join(spans)
|
||||||
|
|
||||||
|
|
||||||
|
def details_inner_html(details: str) -> str:
|
||||||
|
"""Colour-highlight diffs; otherwise escape as a plain muted block."""
|
||||||
|
if _looks_like_diff(details):
|
||||||
|
return colorize_diff_html(details)
|
||||||
|
return escape(details)
|
||||||
|
|
||||||
|
|
||||||
def fact_rows(items: Iterable[tuple[str, str, str]]) -> tuple[str, str]:
|
def fact_rows(items: Iterable[tuple[str, str, str]]) -> tuple[str, str]:
|
||||||
"""Build the HTML <table> rows and plain-text lines for the fact list.
|
"""Build the HTML <table> rows and plain-text lines for the fact list.
|
||||||
|
|
||||||
@@ -222,8 +287,8 @@ def render(
|
|||||||
<pre style="margin:0;padding:14px 16px;background:{CANVAS};
|
<pre style="margin:0;padding:14px 16px;background:{CANVAS};
|
||||||
border:1px solid {BORDER};border-radius:8px;
|
border:1px solid {BORDER};border-radius:8px;
|
||||||
color:{MUTED_STRONG};font-family:{FONT_MONO};
|
color:{MUTED_STRONG};font-family:{FONT_MONO};
|
||||||
font-size:12px;line-height:1.55;white-space:pre-wrap;
|
font-size:12px;line-height:1.55;white-space:pre;
|
||||||
word-break:break-word;overflow-x:auto;">{escape(details)}</pre>
|
overflow-x:auto;">{details_inner_html(details)}</pre>
|
||||||
</td></tr>
|
</td></tr>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -240,8 +305,8 @@ def render(
|
|||||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0"
|
||||||
border="0" style="background:{CANVAS};padding:32px 16px;">
|
border="0" style="background:{CANVAS};padding:32px 16px;">
|
||||||
<tr><td align="center">
|
<tr><td align="center">
|
||||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0"
|
<table role="presentation" width="760" cellpadding="0" cellspacing="0"
|
||||||
border="0" style="max-width:600px;width:100%;background:{SURFACE};
|
border="0" style="max-width:760px;width:100%;background:{SURFACE};
|
||||||
border:1px solid {BORDER};border-radius:12px;
|
border:1px solid {BORDER};border-radius:12px;
|
||||||
overflow:hidden;">
|
overflow:hidden;">
|
||||||
<tr><td style="padding:28px 32px 8px 32px;">
|
<tr><td style="padding:28px 32px 8px 32px;">
|
||||||
|
|||||||
Reference in New Issue
Block a user