···11+//! Cross-feature primitives shared by every `atproto-devtool` subcommand.
22+33+pub mod diagnostics;
+52
src/common/diagnostics.rs
···11+//! Shared miette configuration and `NamedSource` helpers.
22+33+use std::sync::Arc;
44+55+use miette::{GraphicalTheme, MietteHandlerOpts, NamedSource};
66+77+// Re-exports so Phase 3 onwards can import `LabeledSpan` / `SourceSpan` from a
88+// single path. Unused in Phase 1 itself; declared here to keep the diagnostics
99+// surface area in one file.
1010+pub use miette::{LabeledSpan, SourceSpan};
1111+1212+/// Install the miette panic hook and graphical report handler.
1313+///
1414+/// Honours `NO_COLOR=1` by dropping to an unstyled theme. Call this exactly once
1515+/// from `main` before any `miette::Result`-returning code runs.
1616+pub fn install_miette_handler(no_color: bool) -> miette::Result<()> {
1717+ // `NO_COLOR` is also respected automatically by miette when set in the
1818+ // environment; passing an explicit theme here covers the `--no-color` flag
1919+ // path without having to touch process-wide env vars (which is `unsafe` in
2020+ // Rust 2024).
2121+ miette::set_hook(Box::new(move |_| {
2222+ let theme = if no_color {
2323+ GraphicalTheme::unicode_nocolor()
2424+ } else {
2525+ GraphicalTheme::unicode()
2626+ };
2727+ Box::new(
2828+ MietteHandlerOpts::new()
2929+ .graphical_theme(theme)
3030+ .context_lines(3)
3131+ .build(),
3232+ )
3333+ }))?;
3434+3535+ // Install miette's panic hook so panics render through the same handler.
3636+ miette::set_panic_hook();
3737+3838+ Ok(())
3939+}
4040+4141+/// Build a `NamedSource` from a name and raw bytes.
4242+///
4343+/// The bytes are cloned into an `Arc<str>`/`Arc<[u8]>` via miette's constructor,
4444+/// so callers may drop the original slice after this returns.
4545+pub fn named_source_from_bytes(name: impl Into<String>, bytes: &[u8]) -> NamedSource<Arc<[u8]>> {
4646+ NamedSource::new(name, Arc::<[u8]>::from(bytes))
4747+}
4848+4949+/// Build a `NamedSource` from a name and a UTF-8 string slice.
5050+pub fn named_source_from_str(name: impl Into<String>, text: &str) -> NamedSource<String> {
5151+ NamedSource::new(name, text.to_string())
5252+}