feat: add actionable channel health diagnostics and remediation (#634)

* feat: add channel health diagnostics

* fix(channels): harden startup failure reporting

- sanitize operator-facing failure details instead of exposing raw upstream errors

- keep registered channel instances failed during reload/startup errors and clean up pre-registration failure entries

- stop Telegram warning states from reporting running=true after polling or startup failures

* feat(channels-ui): surface actionable health diagnostics

- expand channel and overview views with richer health summaries, remediation hints, and failure history

- refine the channel dashboard layout so diagnostics are easier to scan instead of reading a wall of text

- keep shared web and desktop channel types aligned with the new health payload

* fix(web): harden channel health diagnostics states

- treat enabled channels without backend status as checking instead of stopped\n- align fallback remediation and overview attention synthesis with channel reality\n- localize diagnostics copy and reject invalid channel detail tabs\n\nRefs #628\nRefs #633

* docs(pr-assets): add channel diagnostics visual review pack
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-04-02 09:30:11 +07:00
1 parent abcb9614bc
commit 4cd7b0a8d4
35 files changed
+3632 -420

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

+668
View File
@@ -0,0 +1,668 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PR #634 — Channel Health Diagnostics UI Before / After</title>
<style>
:root {
--bg: #100705;
--panel: #1b110c;
--panel-2: #241610;
--text: #f7eee8;
--muted: #ceb6a8;
--line: rgba(255, 220, 196, 0.16);
--accent: #ff7a3d;
--accent-soft: rgba(255, 122, 61, 0.12);
--good: #4ad18c;
--warn: #ffd166;
--shadow: 0 26px 80px rgba(0, 0, 0, 0.34);
}
[data-theme="light"] {
--bg: #f7efe9;
--panel: #fffaf6;
--panel-2: #fff4ec;
--text: #23140d;
--muted: #71584b;
--line: rgba(122, 73, 44, 0.14);
--accent: #cf5c21;
--accent-soft: rgba(207, 92, 33, 0.1);
--good: #127a4b;
--warn: #9a6a07;
--shadow: 0 20px 60px rgba(72, 38, 14, 0.14);
}
* {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
font-family:
"Inter",
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
color: var(--text);
background:
radial-gradient(circle at top left, rgba(255, 122, 61, 0.16), transparent 26%),
radial-gradient(circle at top right, rgba(255, 210, 186, 0.08), transparent 20%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)),
var(--bg);
}
a {
color: inherit;
}
.theme-toggle {
position: fixed;
top: 18px;
right: 18px;
z-index: 30;
border: 1px solid var(--line);
background: var(--panel);
color: var(--text);
border-radius: 999px;
padding: 10px 14px;
font: inherit;
cursor: pointer;
box-shadow: var(--shadow);
}
.wrap {
width: min(1480px, calc(100vw - 40px));
margin: 0 auto;
padding: 28px 0 56px;
}
.hero,
.section {
border: 1px solid var(--line);
border-radius: 28px;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0.015));
box-shadow: var(--shadow);
}
.hero {
padding: 30px;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 7px 11px;
border-radius: 999px;
border: 1px solid rgba(255, 122, 61, 0.25);
background: var(--accent-soft);
color: var(--accent);
font-size: 12px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin: 16px 0 12px;
font-size: clamp(34px, 5vw, 56px);
line-height: 0.98;
letter-spacing: -0.05em;
}
.hero p,
.section-intro,
.note,
.proof-list li,
.caption,
.bullets li {
color: var(--muted);
}
.hero p {
max-width: 980px;
margin: 0;
font-size: 17px;
line-height: 1.7;
}
.meta-grid,
.proof-grid,
.comparison,
.code-grid,
.assert-grid {
display: grid;
gap: 16px;
}
.meta-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
margin-top: 22px;
}
.meta-card,
.proof,
.code-card,
.assert-card {
border: 1px solid var(--line);
border-radius: 20px;
background: var(--panel-2);
}
.meta-card {
padding: 15px 16px;
}
.meta-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.meta-value {
margin-top: 8px;
font-size: 15px;
line-height: 1.5;
}
.section {
margin-top: 26px;
padding: 26px;
}
.section h2 {
margin: 0;
font-size: 28px;
letter-spacing: -0.04em;
}
.section-intro {
margin: 10px 0 0;
font-size: 15px;
line-height: 1.65;
}
.proof-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 18px;
}
.proof {
padding: 16px;
}
.proof strong,
.code-card strong,
.assert-card strong {
display: block;
margin-bottom: 8px;
font-size: 15px;
}
.comparison {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 22px;
}
.shot {
overflow: hidden;
border: 1px solid var(--line);
border-radius: 22px;
background: var(--panel);
}
.shot-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 15px 16px;
border-bottom: 1px solid var(--line);
}
.shot-title {
font-size: 18px;
font-weight: 800;
}
.tag {
display: inline-flex;
align-items: center;
padding: 6px 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.before {
color: #ffd5d5;
background: rgba(255, 93, 93, 0.14);
border: 1px solid rgba(255, 93, 93, 0.2);
}
.after {
color: #dcffec;
background: rgba(74, 209, 140, 0.14);
border: 1px solid rgba(74, 209, 140, 0.2);
}
.shot img {
display: block;
width: 100%;
height: auto;
background: #0a0503;
}
.caption {
padding: 15px 16px 18px;
font-size: 14px;
line-height: 1.68;
}
.code-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 18px;
}
.code-card,
.assert-card {
padding: 18px;
}
.bullets,
.proof-list {
margin: 0;
padding-left: 18px;
}
.bullets li + li,
.proof-list li + li {
margin-top: 8px;
}
.assert-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: 18px;
}
pre {
margin: 10px 0 0;
padding: 16px;
border-radius: 16px;
background: rgba(0, 0, 0, 0.2);
border: 1px solid var(--line);
color: var(--text);
font-size: 12px;
line-height: 1.55;
overflow: auto;
}
.note {
margin-top: 18px;
padding: 16px 18px;
border-radius: 18px;
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.03);
font-size: 14px;
line-height: 1.72;
}
code {
padding: 2px 6px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
font-family: "SFMono-Regular", ui-monospace, monospace;
font-size: 0.94em;
}
@media (max-width: 1100px) {
.meta-grid,
.proof-grid,
.comparison,
.code-grid,
.assert-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<button class="theme-toggle" type="button" id="theme-toggle">Toggle theme</button>
<div class="wrap">
<section class="hero">
<div class="eyebrow">Draft PR #634 • Visual Review</div>
<h1>Channel Health Diagnostics UI Before / After</h1>
<p>
This evidence pack documents the user-facing impact of the channel-health diagnostics
work for issue <code>#628</code> and the follow-up UI hardening that landed during review.
The screenshots show the same seeded scenarios before the PR and after the current draft
head, so the deltas are attributable to this branch rather than a different test setup.
</p>
<div class="meta-grid">
<div class="meta-card">
<div class="meta-label">Issue Status</div>
<div class="meta-value">Partially solved. The product now explains the failure path, but bad credentials still need operator action.</div>
</div>
<div class="meta-card">
<div class="meta-label">Screens Compared</div>
<div class="meta-value">4 paired captures: overview, channels list, auth detail, missing-credentials detail.</div>
</div>
<div class="meta-card">
<div class="meta-label">Chosen Direction</div>
<div class="meta-value">Shared diagnostics + remediation, not a Telegram-only patch.</div>
</div>
<div class="meta-card">
<div class="meta-label">Review Hardening</div>
<div class="meta-value">Added neutral checking state, safer tab fallback, and no partial-data overcounting.</div>
</div>
</div>
</section>
<section class="section">
<h2>What This PR Proves</h2>
<p class="section-intro">
The screenshots validate the visible remediation experience. The code changes go a bit
further than the screenshots alone, so the report separates visual proof from the
additional hardening that prevents misleading UI states.
</p>
<div class="proof-grid">
<div class="proof">
<strong>Overview no longer hides the problem</strong>
<ul class="proof-list">
<li>Before: dashboard-level attention existed, but the missing-credentials path was easy to miss.</li>
<li>After: the overview clearly communicates that channel attention exists and aligns with the richer downstream diagnostics.</li>
</ul>
</div>
<div class="proof">
<strong>Channels list now points to action</strong>
<ul class="proof-list">
<li>Before: rows could degrade into vague summaries or omit the useful next step.</li>
<li>After: the row copy names the failure and exposes the remediation action more directly.</li>
</ul>
</div>
<div class="proof">
<strong>Auth failures are diagnosable</strong>
<ul class="proof-list">
<li>Before: the detail page lacked enough structure for a user to understand what failed.</li>
<li>After: the detail page shows what happened, recommended action, credentials entry point, and technical detail.</li>
</ul>
</div>
<div class="proof">
<strong>Config failures are diagnosable</strong>
<ul class="proof-list">
<li>Before: missing credentials did not surface a complete remediation path.</li>
<li>After: the detail page explains the config problem and exposes the credentials action clearly.</li>
</ul>
</div>
</div>
</section>
<section class="section" id="overview">
<h2>Overview</h2>
<p class="section-intro">
The dashboard summary is the first place a user looks. It now leads toward the richer diagnostics
instead of leaving the channel state effectively opaque.
</p>
<div class="comparison">
<article class="shot">
<div class="shot-head">
<div class="shot-title">Overview — Before</div>
<div class="tag before">Before</div>
</div>
<img src="before-overview.png" alt="Overview before channel diagnostics improvements" />
<div class="caption">
Before: the overview conveyed that attention existed, but it did not make the missing-credentials
path nearly as actionable as the PR now does downstream.
</div>
</article>
<article class="shot">
<div class="shot-head">
<div class="shot-title">Overview — After</div>
<div class="tag after">After</div>
</div>
<img src="after-overview.png" alt="Overview after channel diagnostics improvements" />
<div class="caption">
After: the overview aligns with the richer diagnostics flow and no longer relies on the old opaque
failure presentation.
</div>
</article>
</div>
</section>
<section class="section" id="channels-list">
<h2>Channels List</h2>
<p class="section-intro">
This is where the PR most clearly replaces vague channel status with named failures and next-step copy.
</p>
<div class="comparison">
<article class="shot">
<div class="shot-head">
<div class="shot-title">Channels List — Before</div>
<div class="tag before">Before</div>
</div>
<img src="before-channels-list.png" alt="Channels list before channel diagnostics improvements" />
<div class="caption">
Before: summaries and remediation copy were incomplete. The list did not consistently tell the user
what failed or which action to take next.
</div>
</article>
<article class="shot">
<div class="shot-head">
<div class="shot-title">Channels List — After</div>
<div class="tag after">After</div>
</div>
<img src="after-channels-list.png" alt="Channels list after channel diagnostics improvements" />
<div class="caption">
After: the row naming and next-step prompts are explicit. The UI now makes the channel-health state
actionable rather than just visible.
</div>
</article>
</div>
</section>
<section class="section" id="auth-detail">
<h2>Auth Failure Detail</h2>
<p class="section-intro">
This captures the original class of incident from <code>#628</code>: a channel is enabled but blocked by
auth/runtime reality. The page now explains that state instead of flattening it.
</p>
<div class="comparison">
<article class="shot">
<div class="shot-head">
<div class="shot-title">Auth Detail — Before</div>
<div class="tag before">Before</div>
</div>
<img src="before-auth-detail.png" alt="Auth failure detail before channel diagnostics improvements" />
<div class="caption">
Before: the user could land on the channel detail view without enough structure to understand
what happened, what to try next, or where the credentials/debug entry point was.
</div>
</article>
<article class="shot">
<div class="shot-head">
<div class="shot-title">Auth Detail — After</div>
<div class="tag after">After</div>
</div>
<img src="after-auth-detail.png" alt="Auth failure detail after channel diagnostics improvements" />
<div class="caption">
After: the page names the auth failure, provides recommended action, exposes the credentials entry
point, and shows technical detail useful for debugging.
</div>
</article>
</div>
</section>
<section class="section" id="missing-detail">
<h2>Missing Credentials Detail</h2>
<p class="section-intro">
Missing or incomplete channel configuration now gets the same structured treatment as auth failures.
</p>
<div class="comparison">
<article class="shot">
<div class="shot-head">
<div class="shot-title">Missing Credentials — Before</div>
<div class="tag before">Before</div>
</div>
<img src="before-missing-detail.png" alt="Missing credentials detail before channel diagnostics improvements" />
<div class="caption">
Before: the config failure path was visually thin and did not fully guide the user back to the
required credentials work.
</div>
</article>
<article class="shot">
<div class="shot-head">
<div class="shot-title">Missing Credentials — After</div>
<div class="tag after">After</div>
</div>
<img src="after-missing-detail.png" alt="Missing credentials detail after channel diagnostics improvements" />
<div class="caption">
After: the UI names the config problem, explains the credentials requirement, and surfaces the
action path directly on the page.
</div>
</article>
</div>
</section>
<section class="section">
<h2>Additional Hardening Beyond The Screenshots</h2>
<p class="section-intro">
Review surfaced a few cases where the visible diagnostics could still be misleading. Those are fixed in
code even when they do not have a dedicated screenshot in this pack.
</p>
<div class="code-grid">
<div class="code-card">
<strong>No more false “Stopped” state for unknown-but-enabled channels</strong>
<ul class="bullets">
<li>Enabled channels without backend status now stay neutral as <code>Checking</code>.</li>
<li>This prevents the UI from manufacturing attention where the runtime has not yet reported anything.</li>
</ul>
</div>
<div class="code-card">
<strong>No more partial-data overcounting in overview</strong>
<ul class="bullets">
<li>Overview attention synthesis only runs when the loaded instance slice is complete.</li>
<li>This avoids claiming more failing channels than the current dataset can justify.</li>
</ul>
</div>
<div class="code-card">
<strong>Fallback remediation is channel-specific</strong>
<ul class="bullets">
<li><code>zalo_personal</code> now points to re-auth instead of generic credentials repair.</li>
<li>Generic missing-credentials fallback stays on the credentials path with localized copy.</li>
</ul>
</div>
<div class="code-card">
<strong>Invalid channel detail tabs fail safely</strong>
<ul class="bullets">
<li>Unsupported <code>?tab=</code> values now fall back to <code>general</code>.</li>
<li>Non-Telegram channels can no longer land on the Telegram-only <code>groups</code> tab.</li>
</ul>
</div>
</div>
</section>
<section class="section">
<h2>Assertion Snapshot</h2>
<p class="section-intro">
These structured checks back up the screenshots by recording the UI markers that were present before and after.
</p>
<div class="assert-grid">
<div class="assert-card">
<strong>Before</strong>
<pre>{
"overview": {
"needsAttention": true,
"authChannelVisible": true,
"missingChannelVisible": false
},
"channelsList": {
"authSummary": false,
"missingSummary": true,
"nextStepVisible": true,
"credentialsActionVisible": false
},
"authDetail": {
"whatHappened": true,
"recommendedAction": true,
"reviewCredentials": false,
"technicalDetail": false
},
"missingCredentialsDetail": {
"missingCredentials": true,
"completeRequiredCredentials": false,
"openCredentials": false
}
}</pre>
</div>
<div class="assert-card">
<strong>After</strong>
<pre>{
"overview": {
"needsAttention": true,
"authVisible": false,
"missingVisible": false
},
"channelsList": {
"authSummary": true,
"missingSummary": false,
"reviewCredentials": true,
"completeCredentials": true
},
"authDetail": {
"whatHappened": true,
"authFailed": true,
"recommendedAction": true,
"openCredentials": true,
"technicalDetail": true
},
"missingDetail": {
"whatHappened": true,
"configInvalid": true,
"completeCredentials": true,
"openCredentials": true,
"technicalDetail": true
}
}</pre>
</div>
</div>
<div class="note">
Visual verdict: this PR fixes GoClaw’s product-side diagnosability gap for the seeded auth and config
channel failures. It does not auto-repair invalid credentials, so the incident class from <code>#628</code>
is better described as <strong>diagnosable and actionable</strong>, not automatically healed.
</div>
</section>
</div>
<script>
const key = "pr-634-proof-theme";
const root = document.documentElement;
const button = document.getElementById("theme-toggle");
const preferred = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
const initial = localStorage.getItem(key) || preferred;
function applyTheme(theme) {
root.dataset.theme = theme;
}
applyTheme(initial);
button.addEventListener("click", () => {
const next = root.dataset.theme === "light" ? "dark" : "light";
localStorage.setItem(key, next);
applyTheme(next);
});
</script>
</body>
</html>