Skip to main content

diagnostics

Error handling and build-time diagnostics conventions - Result-not-panic, structured Diagnostics with stable codes, actionable help, color/JSON presentation layering, exit codes, and cold-path performance.

Source facts

Repository
microsoft/webui
Last source activity
September 26, 2026 at 00:46
Detected SKILL.md language
English
Stars
98
Forks
24

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
diagnostics
description
Error handling and build-time diagnostics conventions - Result-not-panic, structured Diagnostics with stable codes, actionable help, color/JSON presentation layering, exit codes, and cold-path performance.
# Error Handling & Diagnostics Use this skill whenever you add, change, or review an error path: a build-time authoring error, a parser/handler failure, a CLI validation error, or anything surfaced to a host (FFI/WASM/Node) or a tool/agent. WebUI errors must be **recoverable**, **actionable**, and **machine-consumable** — for humans and AI agents alike. ## 1 - Never panic on recoverable input `panic = "abort"` in the release profile means a panic **kills the process instantly** — including any FFI/WASM/Node host embedding the framework. Bad template input, bad CLI input, and bad state are *recoverable* and must return `Result`, never `panic!`/`unwrap()`/`expect()`. | Situation | Do | |-----------|----| | Malformed template / CSS / route authored by a developer | Return `Result` with a structured `Diagnostic` (see §3). | | Missing/invalid CLI input (file, port, flag) | Return a typed `CliError` (see §6). | | A genuinely impossible internal state | Prefer `?` with a typed error; only use `unreachable!`/`expect` with a justification comment, and never in a hot or host-reachable path. | `unwrap()`/`expect()` are banned in library code (`clippy.toml` `disallowed-methods`). `todo!`, `unimplemented!`, and `dbg!` are banned workspace-wide (`clippy.toml` `disallowed-macros`). Tests opt out with `#[allow(clippy::disallowed_methods)]`. > **Enforcement note.** `unwrap`/`expect`/`todo!`/`unimplemented!`/`dbg!` are > caught by clippy. `panic!` is *not* lint-banned (too entrenched) — keep it out > of recoverable paths by review. The "no regex in core logic" rule is also > review-enforced, not `deny`-banned: `actix-web` pulls `regex` transitively, so > a crate-level ban would break the build and can't scope to first-party code. ## 2 - Error type conventions | Crate kind | Error type | |-----------|-----------| | Library (`webui-parser`, `webui-handler`, `webui-expressions`, `webui-state`, `webui-protocol`, `webui-ffi`) | Custom `enum` via `thiserror`. | | Binary (`webui-cli`, `xtask`) | `anyhow` for orchestration; a typed `enum` when callers must branch on the cause (e.g. `webui-cli`'s `CliError` for hints + exit codes). | - Each error layer's `Display` describes **only its own level**; the `#[source]` chain carries the rest, so `anyhow`'s `{:#}` never double-prints. Provide a flat `chain_message()` helper for hosts that don't walk the chain (Node, FFI). - Add **dedicated variants** instead of overloading a generic `Generic(String)` / `Validation(String)` so callers can `match` programmatically. ## 3 - Authoring errors are structured `Diagnostic`s Every "the developer wrote invalid template syntax" mistake is returned as `ParserError::Template(Box<Diagnostic>)` (`crates/webui-parser/src/diagnostic.rs`), so all build errors render identically. A `Diagnostic` carries: - **`code`** — a stable, machine-readable identifier (e.g. `invalid-for-each`). Defined in `diagnostic::codes`. Treat codes as a **stable API**: tools and AI agents branch on them, so rename only with a deliberate migration. - **title** — short, lowercase (`invalid <for> each expression`). - **location** — set from a byte offset via `.at_offset(source, offset)`; rendered rustc-style `--> owner:line:column` (single forward scan, **no regex, no recursion**), falling back to `in component <c> · element <e>`. - **snippet** — the offending source text. - **`help:`** — an actionable fix (see §4). Add a new authoring error with the parser helpers (`authoring_error`, `authoring_error_at`, `html_error`) and a new constant in `diagnostic::codes`. Validate at **parse/build time and fail fast** — never defer to render time. ## 4 - Make errors actionable (and typo-aware) Tell the developer **what** is wrong *and* **how** to fix it. Every `Diagnostic` should carry a `help:` line. Where a mistake is likely a typo, suggest the intended name via `suggest::closest_match` (iterative Levenshtein — **no recursion, no regex**, cold path only): - Misspelled directive attribute: `<for eahc=…>` -> "did you mean `each`?" - Unknown component tag: suggest the closest **same-namespace** registered component (`<mp-buton>` -> `<mp-button>`). **Prefix-guard** the match (text before the first `-` must match) so a genuine third-party custom element (`<md-button>`) is never falsely flagged. ## 5 - Presentation layering: color belongs ONLY in the entry point Libraries produce **plain, color-free data**. The entry point decides how to present it. Never embed ANSI in library output or in any machine/host channel. | Consumer | Gets | |----------|------| | `webui-cli` (terminal) | Reads `Diagnostic` fields and colorizes with `console::style()` — the **only** approved styling method (see copilot-instructions "Terminal output styling"). | | FFI / WASM / Node | The plain `Display` text through their native error channel (`webui_last_error`, `JsValue`, `napi::Error`). | | Browser / tools (dev-server live-reload, SSE, `console.error`) | **Plain** text. ANSI renders as garbage and breaks single-line SSE frames. | When one value feeds both a terminal and a non-terminal channel, split it: `webui-dev-server`'s `RebuildError { display, message }` carries a colorized `display` for the reporter and a plain `message` for the browser. Per-line color: when colorizing multi-line output, style **each line independently** (open + close the SGR span within the line). A single span that straddles newlines bleeds when the line is later re-prefixed (e.g. `[server]` under `xtask dev`). ## 6 - Machine-readable output and exit codes (`webui-cli`) For editors, CI, and AI/agent tooling: - `--format json` (global flag) emits each error as one JSON object on **stdout** (no ANSI; decorative output suppressed): `{severity, code, message, file, line, column, snippet, help, chain}`. Branch on the stable `code`, not the human `message`. Build the object with the `serde_json::Map` API, **not** the `json!` macro (it `unwrap`s internally and trips `disallowed_methods`). - Exit codes follow BSD `sysexits.h` (`webui-cli`'s `error::exit_code`): `65` data/authoring error, `66` missing input, `69` port in use, `74` I/O, `2` usage (clap), `1` otherwise. - Replace fragile `err_msg.contains("...")` dispatch with typed errors that own their `hint()` and `exit_code()`. ## 7 - Error construction is COLD - keep it off the hot path Building a `Diagnostic` (format strings, suggestions, location scans) is rare, but if it inlines into a hot function it bloats that function and perturbs its code layout — a real, measurable regression (observed ~4-5% on parse benches with **no** added hot-path work). - Mark error builders `#[cold]` + `#[inline(never)]` (e.g. `authoring_error*`, `html_error`, `css_diagnostic`, `*_error` constructors, `suggest::closest_match`). - Keep hot fast-paths inlinable: a per-element check (e.g. `split_once('-')`) must stay inlined; only its cold fallback (the registry scan) goes out-of-line. - Validate layout-sensitive changes with `cargo bench -p <crate>` against the base branch (see `skills/perf/SKILL.md`). A "regression" with no added compute is usually layout — fix it with `#[cold]`, don't shrug it off. ## 8 - Checklist for a new error - [ ] Returned as `Result`, never panicked (host-safe under `panic = "abort"`). - [ ] Authoring mistake -> `ParserError::Template(Box<Diagnostic>)` with a new `diagnostic::codes` constant, location, snippet, and `help:`. - [ ] `help:` is actionable; add a "did you mean …?" suggestion if it's a typo. - [ ] No color/ANSI in library, host, browser, or JSON output. - [ ] Surfaced in `--format json` with the stable `code`; exit code classified. - [ ] Error construction is `#[cold]`/`#[inline(never)]`; hot path unchanged (confirm with a benchmark if it sits near a hot loop). - [ ] Tests: a regression test that fails without the error, asserting on the `code` (not the prose); JSON stays plain (no `\x1b`). - [ ] `DESIGN.md` updated if the error-handling architecture changed; public docs (incl. `docs/ai.md`) updated if a user-visible code/flag changed.
View on GitHub