From 9ea777d7b72fedede9a05fb1b34d3183b3486ab9 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 1 May 2026 18:42:24 +0300 Subject: [PATCH] internal/gui: surface warn status + new voice tests in Classic UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend now renders three-tier status (passed/warn/failed) instead of two. Warn rows get a yellow exclamation-mark dot, expand by default to show the hint, and contribute to a new "(with warnings)" suffix on the "All checks passed" header. Test catalog gains "voice-quality" and "voice-srv" rows replacing the single "stun" row, in the same position (after udp, before api). RU descriptions explain what each test actually probes. useDrover's lastSummary now reports {total, failed, warnings} so the Classic header can pick the right tier color. App.go counts StatusWarn as passed in the final {passed, failed} summary emitted via "check:done" — warn is a soft pass per spec. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/gui/app.go | 8 +++-- .../gui/frontend/src/components/Classic.jsx | 21 +++++++++----- .../gui/frontend/src/components/shared.jsx | 29 ++++++++++++------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/internal/gui/app.go b/internal/gui/app.go index 5e76be2..c7e26ee 100644 --- a/internal/gui/app.go +++ b/internal/gui/app.go @@ -68,8 +68,8 @@ type Config struct { // for them on the "check:result" event. Mirrors checker.Result but with // Duration converted to milliseconds (int) for the JS side. type CheckResult struct { - ID string `json:"id"` // tcp / greet / auth / connect / udp / stun / api - Status string `json:"status"` // running | passed | failed | skipped + ID string `json:"id"` // tcp / greet / auth / connect / udp / voice-quality / voice-srv / api + Status string `json:"status"` // running | passed | warn | failed | skipped Metric string `json:"metric,omitempty"` Error string `json:"error,omitempty"` Hint string `json:"hint,omitempty"` @@ -136,7 +136,9 @@ func (a *App) RunCheck(cfg Config) { Attempt: r.Attempt, }) switch r.Status { - case checker.StatusPassed: + case checker.StatusPassed, checker.StatusWarn: + // Warn is a "soft pass" — counted as passed for the + // final summary, but the row still surfaces the hint. passed++ case checker.StatusFailed: failed++ diff --git a/internal/gui/frontend/src/components/Classic.jsx b/internal/gui/frontend/src/components/Classic.jsx index fa97bff..c84bf3a 100644 --- a/internal/gui/frontend/src/components/Classic.jsx +++ b/internal/gui/frontend/src/components/Classic.jsx @@ -65,7 +65,7 @@ export function ClassicWindow({ mode = 'dark', initial, onToggleMode }) { const D = useDrover(initial); const [version, setVersion] = React.useState(''); React.useEffect(() => { GoVersion().then(setVersion).catch(() => {}); }, []); - const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip }; + const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip, warn: t.warn }; const fontMono = '"JetBrains Mono","SF Mono",ui-monospace,Menlo,Consolas,monospace'; const fontUI = '"Inter","Segoe UI",system-ui,sans-serif'; const isActive = D.phase === 'active'; @@ -288,7 +288,9 @@ function ClassicStatus({ t, D, palette, fontMono }) { : D.lastSummary?.failed === 0 - ? All checks passed. Ready to start. + ? (D.lastSummary?.warnings > 0 + ? All checks passed (with warnings). + : All checks passed. Ready to start.) : {D.lastSummary?.failed} of {D.tests.length} checks failed. Some features won't work.} {/* tests */} @@ -308,23 +310,26 @@ function ClassicStatus({ t, D, palette, fontMono }) { {test.label} + color: state === 'failed' ? t.danger : state === 'warn' ? t.warn : state === 'skipped' ? t.skip : t.dim }}> {r?.metric || (state === 'running' ? '...' : '')} - {r?.result === 'failed' && ( + {(r?.result === 'failed' || r?.result === 'warn') && ( )} - {r?.result === 'failed' && r.expanded && ( + {(r?.result === 'failed' || r?.result === 'warn') && r.expanded && (
-
{r.error}
-
{r.hint}
+ {r.error + ?
{r.error}
+ : (r.hint &&
{r.hint}
)} + {r.error &&
{r.hint}
} {r.rawHex && (
t.id); const failed = ids.filter(id => results[id]?.result === 'failed').length; - return { total: ids.length, failed }; + const warnings = ids.filter(id => results[id]?.result === 'warn').length; + return { total: ids.length, failed, warnings }; }, [phase, results, tests]); // ── actions ──────────────────────────────────────────────────────────── @@ -123,10 +125,10 @@ export function useDrover(initial = {}) { hint: r.hint, rawHex: r.rawHex, attempt: r.attempt, - expanded: r.status === 'failed', + expanded: r.status === 'failed' || r.status === 'warn', }, })); - pushLog(r.status === 'failed' ? 'ERROR' : r.status === 'skipped' ? 'WARN' : 'INFO', + pushLog(r.status === 'failed' ? 'ERROR' : (r.status === 'skipped' || r.status === 'warn') ? 'WARN' : 'INFO', `${r.id}: ${r.status}${r.metric ? ' · ' + r.metric : ''}`); }); const offDone = EventsOn('check:done', (s) => { @@ -332,6 +334,13 @@ export function StatusDot({ state, palette, size = 12 }) { ; } + if (state === 'warn') { + return + + + + ; + } if (state === 'skipped') { return