aboutsummaryrefslogtreecommitdiff
path: root/CHANGELOG.md
diff options
context:
space:
mode:
Diffstat (limited to 'CHANGELOG.md')
-rw-r--r--CHANGELOG.md1253
1 files changed, 1242 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9982cc0..e216f6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,7 +11,9 @@ Any contributor making changes to the project must record their changes in this
**- Milestone.1 "MVP" -**
- [Unreleased](#unreleased)
-- [Release 0.3.0 (Unreleased)](#release-030-unreleased)
+- [Release 0.5.0 (Unreleased)](#release-050-unreleased)
+- [Release 0.4.0 (2026-08-16)](#release-040-2026-08-16)
+- [Release 0.3.0 (2026-07-27)](#release-030-2026-07-27)
- [Release 0.2.2 (2026-07-10)](#release-022-2026-07-10)
- [Release 0.2.1 (2026-07-01)](#release-021-2026-07-01)
- [Release 0.2.0 (2026-06-30)](#release-020-2026-06-30)
@@ -50,7 +52,1038 @@ None
---
-### Release 0.3.0 (Unreleased)
+## Contents
+
+### 0.5.0 (Unreleased)
+
+#### Fixes:
+
+None
+
+#### Optimizations:
+
+None
+
+#### Features:
+
+1. **[`macros:wrap`]** Added the `#[derive(Wrap)]` derive macro that treats a struct as its inner (wrapped) type. The macro generates:
+
+ - `From<Inner> for Self` — construct the wrapper from the inner value
+ - `From<Self> for Inner` — unwrap back to the inner value (i.e. `Into<Inner>`)
+ - `Deref` / `DerefMut` — delegate all methods to the inner value
+
+ **Inner field selection:**
+
+ - Tuple struct with one field → that field is the inner type
+ - Named struct with one field → that field is the inner type
+ - Named struct with multiple fields → mark exactly one field with `#[wrap]`; the remaining fields are initialized with `Default::default()` when constructing via `From<Inner>`
+
+ **Example:**
+
+ ```rust,ignore
+ use mingling::macros::Wrap;
+
+ #[derive(Wrap)]
+ struct Name(String);
+
+ #[derive(Wrap)]
+ struct Greeting {
+ name: String,
+ }
+
+ #[derive(Wrap)]
+ struct Task {
+ #[wrap]
+ content: String,
+ done: bool,
+ }
+
+ let name = Name::from("Mingling".to_string());
+ // `Deref` forwards methods to the inner `String`
+ assert_eq!(name.len(), 8);
+ let inner: String = name.into();
+ ```
+
+ The macro is re-exported from `mingling::Wrap` and `mingling::prelude::Wrap` (feature-gated behind `macros`).
+
+2. **[`macros:completion`]** Reworked the `#[completion]` attribute macro to accept a relaxed signature and fixed several code-generation details:
+
+ **Relaxed function signature:**
+ - **Context parameter is now optional.** Previously, the completion function was required to have exactly one parameter of type `&ShellContext`. Now the first parameter (if present) may be `&ShellContext`, an owned `ShellContext`, or **any type implementing `From<&ShellContext>`**. The macro binds the shell context to the declared parameter type via `<#ty as From<&ShellContext>>::from(ctx)`, so identity `From` covers `&ShellContext` itself and `From<&Self>` covers owned `ShellContext` (a new `impl From<&Self> for ShellContext` added in `mingling_core/src/comp/shell_ctx.rs`). With **no parameters at all**, the completion function simply ignores the shell context.
+ - **Resource injection after the context.** `extract_resources_from_args` now starts after the context parameter (index 0 when present, index 0 when absent). A completion function with no context parameter cannot inject resources — the macro emits a compile error in that case.
+ - **Return type is now `Into<Suggest>`.** Previously the function had to return `Suggest` exactly. Now any type implementing `Into<Suggest>` is valid — `Suggest` itself, `Vec<String>`, `Vec<(String, String)>` (suggestion + description), `&[&str]`, or a set of `SuggestItem`s. A `()` return (or no return type) is also accepted and mapped to an empty `Suggest`.
+ - **`SuggestItem` gains `From<&str>`** (in `mingling_core/src/comp/suggest.rs`), and the blanket `From<T> for Suggest where T: IntoIterator` was widened from `T::Item: Into<String>` to `T::Item: Into<SuggestItem>`, so iterators of `&str`, `String`, or `SuggestItem` all convert to `Suggest` uniformly.
+
+ **Generated `Completion::comp` signature:** The generated `fn comp` now always returns `::mingling::Suggest` and always binds the ambient `ctx: &ShellContext` parameter (which the caller passes via `Completion::comp(&ctx)`), ignoring it when the user function takes no context. The generated body:
+
+ - Declares `let _ = ctx;` when the function takes no context parameter (keeps the parameter used).
+ - Declares `let __ctx: #ty = <#ty as From<&ShellContext>>::from(ctx);` when a context parameter is present, then passes `__ctx` as the first argument.
+ - Wraps the user body; for `()` returns, evaluates the body then returns `Suggest::new()`; otherwise evaluates the body and converts the result via `Into::into`.
+
+ **`ShellContext` is now `Clone`** (derive added in `mingling_core/src/comp/shell_ctx.rs`) and implements `From<&Self> for ShellContext`, enabling owned-context completion signatures.
+
+ _No behavioral change for existing code that already used the classic `fn(ctx: &ShellContext) -> Suggest` form — the identity `From` and `Into` impls preserve that path exactly.
+
+#### **BREAKING CHANGES** (API CHANGES):
+
+1. **[`core:comp`]** **[`macros:dispatch_tree`]** **[BREAKING RENAME]** Renamed the prefix-tree dispatch method `dispatch_args_trie` to `dispatch_args` across the codebase.
+
+ **Renamed methods:**
+
+ - `ProgramCollect::dispatch_args_trie` → `ProgramCollect::dispatch_args` (trait definition in `mingling_core/src/program/collection.rs`)
+ - `Program::dispatch_args_trie` → `Program::dispatch_args` (in `mingling_core/src/program.rs`)
+ - `MockProgramCollect::dispatch_args_trie` → `MockProgramCollect::dispatch_args` (in `mingling_core/src/program/collection/mock.rs`)
+ - The `dispatch_args_trie` function body generated by `dispatch_tree_gen.rs` now emits a `dispatch_args` method instead.
+ - All call sites updated: `mingling_core/src/program/exec.rs`, `mingling_macros/src/systems/dispatch_tree_gen.rs`, and two call sites in `mingling_core/src/comp.rs` (both in the `CompletionHelper::complete` method and in the `entry_description` helper's dispatch-tree branch).
+
+ **Migration guide:**
+
+ - Any code calling `C::dispatch_args_trie(...)` or `program.dispatch_args_trie(...)` must now call `C::dispatch_args(...)` or `program.dispatch_args(...)`.
+ - Any manual `ProgramCollect` implementation (e.g., in tests or mocks) must rename the method from `dispatch_args_trie` to `dispatch_args`.
+
+ _No behavioral changes — this is a pure rename of the prefix-tree dispatch method. The method's semantics, signature, and dispatch-tree behavior are unchanged; only its name dropped the redundant `_trie` suffix.
+
+2. **[`macros:dispatcher`]** **[BREAKING]** Dispatchers are now always registered at compile time — the `with_dispatcher` / `with_dispatchers` dynamic registration API on `Program` has been removed entirely.
+
+ ### What changed
+
+ Previously, the `dispatch_tree` feature controlled _whether_ dispatchers were collected at compile time. With the feature enabled, `dispatcher!` (and the related `dispatcher_clap!`, `#[command]`, and completion macros) emitted a `__internal_dispatcher_*` static and registered the node in the global `COMPILE_TIME_DISPATCHERS` registry via `register_dispatcher!`; `gen_program!` then built a `dispatch_args` trie from that registry. Without the feature, users had to manually register dispatchers at runtime via `program.with_dispatcher(CMDGreet)`.
+
+ Now, **all dispatchers are always collected at compile time** regardless of the `dispatch_tree` feature:
+
+ - `dispatcher!`, `dispatcher_clap!`, `#[command]`, and the `comp`-generated completion dispatcher **always** emit the `__internal_dispatcher_*` static and call `register_dispatcher!`.
+ - `gen_program!` always reads `COMPILE_TIME_DISPATCHERS` and generates both `ProgramCollect::dispatch_args` and `ProgramCollect::get_nodes` from it.
+ - The `dispatch_tree` feature now only selects the _internal matching strategy_: a char-level trie when enabled, a linear longest-prefix list otherwise. Both strategies are generated from the same compile-time-collected entries.
+ - The `Program.dispatcher` field and the `with_dispatcher` / `with_dispatchers` methods (and the deprecated `Dispatchers` multi-registration helper) have been **removed**.
+
+ ### Removed API
+ - `Program::with_dispatcher<Disp>(&mut self, dispatcher: Disp) -> &mut Self` — **removed**
+ - `Program::with_dispatchers<D>(&mut self, dispatchers: D) -> &mut Self` — **removed** (already deprecated)
+ - `Dispatchers<G>` struct and all its `From` tuple impls (up to 7 elements), `Deref`, and `Into<Vec<_>>` conversions — **removed** (already deprecated)
+ - `Program::dispatch_args_dynamic(...)` — **removed** (renamed to `dispatch_args` in **BREAKING CHANGE #1**)
+ - `ProgramCollect::dispatch_args` no longer has a fallback default body and is now a required (non-optional) method — any manual `ProgramCollect` impl (tests, mocks, etc.) must implement both `dispatch_args` and `get_nodes`.
+
+ ### New internal module
+
+ A new `dispatch_list_gen` module was added to `mingling_macros` (`systems/dispatch_list_gen.rs`) providing `gen_dispatch_args`, which generates a linear `dispatch_args` body used when the `dispatch_tree` feature is **disabled**. It sorts nodes by display-name length (longest first) so the first matching node is the most specific one, mirroring the old dynamic dispatcher's "longest registered prefix wins" rule:
+
+ ```rust,ignore
+ fn dispatch_args(
+ raw: &[String],
+ ) -> Result<AnyOutput<Self::Enum>, ProgramInternalExecuteError> {
+ let raw_string = format!("{} ", raw.join(" "));
+ // ... linear if-chain over each node, longest prefix first ...
+ Ok(Self::build_entry_fallback(raw.to_vec()))
+ }
+ ```
+
+ `dispatch_tree_gen::gen_dispatch_args_trie` continues to provide the trie strategy (and now also exposes the shared `gen_get_nodes` helper, moved from `dispatch_tree_gen` into `program_final_gen`).
+
+ ### pathf changes
+ - **`mingling_pathf::config::PathfinderConfig`** — **removed** (deleted `config.rs`). The `use_dispatch_tree` flag no longer exists.
+ - **`pattern_analyzer::init_with_config(config)`** — **removed**; `init()` now always registers `DispatcherPattern` / `DispatcherClapPattern` with compile-time collection enabled.
+ - **`DispatcherPattern` / `DispatcherClapPattern`** — no longer carry a `use_dispatch_tree` field (`new()` takes no arguments). Both patterns now always extract the `__internal_dispatcher_*` static for every matched command.
+ - **`mingling_pathf::analyze_and_build_type_mapping_for` / `analyze_and_build_type_mapping`** — signatures no longer take a `&PathfinderConfig` argument.
+ - **`mingling_core::build::pathf`** — the wrappers no longer pass a config (the `config::*` re-export was removed).
+
+ ### Migration guide
+ - **Remove all `program.with_dispatcher(...)` calls.** Dispatchers are now automatically collected by `gen_program!` — no explicit registration is needed. Examples of affected call sites (all updated in this release): `example-basic`, `example-argument-parse`, `example-argument-picker`, `example-async-support`, `example-clap-binding`, `example-command-macro`, `example-completion`, `example-custom-pickable`, `example-dispatch-tree`, `example-enum-tag`, `example-error-handling`, `example-exitcode`, `example-help`, `example-hook`, `example-implicit-dispatcher`, `example-lazy-resources`, `example-metadata`, `example-outside-type`, `example-pack-err`, `example-panic-unwind`, `example-pathfinder`, `example-repl-basic`, `example-resources`, `example-setup`, `example-structural-renderer`, `example-unit-test`, and `full-todolist`.
+ - **Any manual `ProgramCollect` impl** must now implement both required methods `dispatch_args` and `get_nodes`.
+ - **The `dispatch_tree` feature is now purely an internal optimization** (trie vs. linear-list command matching). It no longer changes whether dispatchers are collected at compile time — that behavior is unconditional. Update any documentation/comments that claimed otherwise.
+ - **The `__internal_dispatcher_*` static and the compile-time registration** are now always emitted, so `pathf`-based `use` imports for these types are unconditional.
+
+ _Behavioral note:_ the runtime behavior of programs is unchanged — all dispatchers registered via `with_dispatcher` are now simply gathered automatically, and the "longest registered prefix wins" matching rule is preserved by both the trie and the linear-list strategies.
+
+3. **[`core`]** **[`macros`]** **[BREAKING REMOVAL]** Removed the `Node` type, the `node!` macro, and the `Dispatcher::node()` / `Dispatcher::clone_dispatcher()` methods. Command path matching is now handled entirely by the compile-time-collected string command names, and dispatchers are identified by a hidden internally-generated `__Dispatcher*` struct.
+
+ ### What changed
+
+ The `Node` struct (in `mingling_core::asset::node`) was a path hierarchy of kebab-cased string segments used by the old dynamic dispatcher to match user input. With dispatchers now always collected at compile time (see **BREAKING CHANGE #2** above), the `Node` type and its supporting machinery became dead code. Command names are now stored and matched as plain string literals during compile-time registration.
+
+ **Removed API:**
+
+ - **`mingling_core::Node`** — Removed the entire `node` module and its public re-export. This includes the struct itself, its `From<&str>` / `From<String>` impls, `join()`, `PartialEq` / `Eq`, `PartialOrd` / `Ord`, `Display`, and `Default`.
+ - **`mingling::macros::node!`** — Removed the `node!` procedural macro. It was only used internally by `dispatcher!` / `dispatcher_clap!` / `#[command]` to construct a `Node` from a dot-separated string; now that `Node` is gone, the macro is obsolete.
+ - **`Dispatcher::node(&self) -> Node`** — Removed from the `Dispatcher` trait. Dispatchers no longer expose a `Node` hierarchy; the command path is embedded in the compile-time registration via `register_dispatcher!("name", ...)`.
+ - **`Dispatcher::clone_dispatcher(&self) -> Box<dyn Dispatcher<C>>`** — Removed from the `Dispatcher` trait, along with the blanket `Clone for Box<dyn Dispatcher<G>>` impl (which relied on `clone_dispatcher`). Dynamic dispatch / boxing of dispatchers is no longer supported.
+ - **`mingling::macros::node` re-export** — Removed from `mingling_macros/src/lib.rs` and `mingling/src/lib.rs`.
+
+ **Dispatcher trait now requires only `begin`:**
+
+ ```rust
+ pub trait Dispatcher<C> {
+ fn begin(&self, args: Vec<String>) -> ChainProcess<C>;
+ }
+ ```
+
+ **Generated dispatcher struct renamed:**
+
+ The `dispatcher!` / `dispatcher_clap!` / `#[command]` macros now generate a **hidden** dispatcher struct named `__Dispatcher{Pascal}` (e.g., `__DispatcherGreet`) instead of the user-facing `CMD*` struct. The struct is marked `#[doc(hidden)]` and `#[allow(nonstandard_style)]`. Users never reference it directly — it is registered at compile time via `register_dispatcher!` and matched purely by its string command name.
+
+ **`Dispatcher` trait example** (from `mingling_core/src/asset/dispatcher.rs` docs):
+
+ ```rust,ignore
+ impl Dispatcher<ThisProgram> for CMDGreet {
+ fn begin(&self, args: Vec<String>) -> ChainProcess<ThisProgram> {
+ Routable::to_chain(Foo { args })
+ }
+ }
+ ```
+
+ **`dispatcher!` syntax change** (reflected in **BREAKING CHANGE #1**'s rename context):
+
+ - Old: `dispatcher!("greet", CMDGreet => EntryGreet)`
+ - New: `dispatcher!("greet", EntryGreet)`
+
+ The `CMD*` is no longer part of the user-facing syntax — the dispatcher struct is generated internally as `__Dispatcher*`. The old form produces a compile error with a migration hint.
+
+ **`dispatcher_clap!` syntax change:**
+
+ - Old: `#[dispatcher_clap("greet", CMDGreet, help = true, error = ErrorGreet)]`
+ - New: `#[dispatcher_clap("greet", help = true, error = ErrorGreet)]`
+
+ `#[dispatcher_clap("greet")]` (bare, no options) is still valid.
+
+ **`#[command]` syntax change:**
+
+ - The `name = CMDName` attribute argument was removed. `#[command(entry = EntryGreet)]` still works; the dispatcher struct is generated internally as `__DispatcherGreet`.
+
+ ### Migration guide
+ - **Replace all `dispatcher!("name", CMDType => EntryType)` calls** with `dispatcher!("name", EntryType)`. The `CMD*` identifier is no longer generated or referenced.
+ - **Replace all `#[dispatcher_clap("name", CMDType, ...)]` attributes** with `#[dispatcher_clap("name", ...)]` (drop the `CMDType` argument).
+ - **Remove `name = CMDName` from `#[command(...)]`** attributes. If you still reference the old generated `CMDName` type (e.g., in `program.with_dispatcher(...)`), remove that call entirely — dispatchers are auto-collected (see **BREAKING CHANGE #2**).
+ - **Remove any `use mingling::Node;` imports** and any code constructing / manipulating `Node` values. If you had custom `Dispatcher` implementations, they must (a) drop the `node()` and `clone_dispatcher()` methods and (b) rely on the compile-time registration (via `dispatcher!` / `dispatcher_clap!` / `#[command]`) rather than manual `with_dispatcher` dynamic dispatch.
+ - **Remove any `node!("...")` macro invocations.** The `node!` macro no longer exists.
+ - **Any manual `Dispatcher` impls** now only need `begin()`. If you previously implemented `node()` for a custom dispatcher used with `program.with_dispatcher(...)`, that whole registration model is removed — see **BREAKING CHANGE #2** for the compile-time-only registration approach.
+
+ _No behavioral changes to command matching — the semantics of dot-separated command paths, kebab-case normalization, and "longest registered prefix wins" are all preserved by the compiled-in string-based dispatch trie / linear list. The removal is purely an API simplification: the `Node` intermediate abstraction and the dispatcher-boxing machinery are gone._
+
+4. **[`feat:parser`]** **[BREAKING REMOVAL]** Removed the legacy `parser` feature and its entire module tree — `mingling::parser` (with `Argument`, `Picker`, `Pickable`, `PickableEnum`, `AsPicker`, `Yes`, `True`, `PathCheckRule`, `PathsChecker`, `PathChecker`, and the built-in `size`-based `usize` size parsing). The `Parser` has been fully superseded by the `picker` feature, which uses the standalone `arg-picker` crate. The `size` crate dependency (only used by the parser's `usize` size-string parsing) has also been removed from `mingling/Cargo.toml`.
+
+ ### What changed
+
+ The `parser` feature provided an internal argument-parsing module (`mingling::parser`) with a fluent `Picker` API for extracting typed values from `Vec<String>` command-line arguments. This functionality has been entirely replaced by the `picker` feature (`arg-picker` crate), so the legacy built-in parser is now dead code and has been removed.
+
+ **Removed public modules and types:**
+
+ - **`mingling::parser` module** — Entire module removed (`mingling/src/parser.rs` and the whole `mingling/src/parser/` directory), including:
+ - **`Argument`** — The struct wrapping `Vec<String>` with `pick_argument`, `pick_arguments`, `pick_flag`, `dump_remains`, and `strip_all_flags` methods.
+ - **`Picker`** — The fluent builder struct with `pick`, `pick_or`, `pick_or_route`, `require`, and `operate_args` methods.
+ - **`Pickable`** — The trait defining `pick(&mut Argument, Flag) -> Option<Self::Output>`.
+ - **`PickableEnum`** — The marker trait for `EnumTag`-implementing enums providing blanket `Pickable` impls.
+ - **`AsPicker`** — The blanket trait implementing `pick`/`pick_or`/`pick_or_route` for all `Into<Vec<String>>` types.
+ - **`Pick1`–`Pick12` / `PickWithRoute1`–`PickWithRoute12`** — Builder structs for chained picks, with `after`, `after_or_route`, `unpack`, `unpack_directly`, and `operate_args`.
+ - **`Yes`** / **`True`** — Explicit boolean-like enums with `is_yes`/`is_no` and `is_true`/`is_false` helpers.
+ - **`PathCheckRule`** / **`PathsChecker`** / **`PathChecker`** — Path validation helpers (`must_file`, `must_dir`, `must_exist`, etc.).
+ - **Built-in `Pickable` impls** — For `String`, `Vec<String>`, all integer/float types, `bool`, `usize` (special size-string parsing like `"25MiB"`), `Vec<usize>`, `Vec<PathBuf>`, `PathBuf`, `Argument`, and `Option<T>`.
+ - The `usize` size-string parsing (e.g. `"25mib"` → `25 * 1024 * 1024`) used the external `size` crate, which has been removed from the dependency tree.
+
+ **Other changes:**
+
+ - **`mingling::features::MINGLING_PARSER`** constant — Removed from `mingling/src/features.rs`.
+ - **`mingling::prelude::AsPicker`** re-export — Removed from the prelude.
+ - **Deleted examples** — Removed `example-argument-parse` and `example-custom-pickable` (and their entries in `docs/example-pages/examples.json`), as they only demonstrated the legacy `parser` API.
+ - **Docs updated** — `docs/pages/6-argument-parse-picker.md` (and `docs/_zh_CN` and `docs/dev` copies) now document the `picker` feature API; the `parser` section of `docs/pages/other/features.md` was removed.
+ - **`mingling/Cargo.toml`** — Removed `parser = ["dep:size"]` from `[features]`, removed `size = { version = "0.5", optional = true }` from `[dependencies]`, and removed `"parser"` from the dev-dependency and example feature lists.
+ - **`mingling/src/lib.rs`** — Removed `#[cfg(feature = "parser")] pub mod parser;` and the prelude's `#[cfg(feature = "parser")] pub use crate::parser::AsPicker;`.
+ - **`mingling/src/parser/` directory** — Entirely deleted (including `args.rs`, `picker.rs`, `picker/builtin.rs`, `picker/bools.rs`, `picker/path.rs`, `picker/path/rule.rs`, and `test.rs`).
+ - **`Cargo.lock`** and per-example `Cargo.lock` files — Removed the `size` package entry and added `arg-picker` / `arg-picker-macros` where the `picker` feature is enabled.
+
+ **Migration guide:**
+
+ - **Replace the `parser` feature with `picker`** in `Cargo.toml`:
+ ```toml
+ # Old:
+ features = ["parser"]
+ # New:
+ features = ["picker"]
+ ```
+ - **Replace API usage** with the `arg-picker` equivalents from the `picker` feature:
+ | Legacy `parser` API | New `picker` API |
+ | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+ | `prev.pick(())` | `prev.pick_or_default(&arg![T])` (or `prev.pick(&arg![T])`) |
+ | `prev.pick(flag)` | `prev.pick(&arg![name: T, 'n'])` |
+ | `prev.pick_or(flag, default)` | `prev.pick_or(&arg![name: T, 'n'], \|\| default)` |
+ | `prev.pick_or_route(flag, route)` + `.unpack()` | `prev.pick_or_route(&arg![T], \|\| route.to_chain())` + `.to_result()` + `route!` |
+ | `args.pick_argument(flag)` | Use `arg_picker::picker::PickerArg` / the `arg!` macro |
+ | `impl Pickable for T { ... }` | `impl SinglePickable for T { fn pick_single(str: Option<&str>) -> PickerArgResult<Self> { ... } }` |
+ | `impl PickableEnum for T {}` on an `EnumTag` enum | Implement `SinglePickable` manually with a `match` |
+ | `.after(...)` | `.post(...)` |
+ | `.unpack()` | `.unwrap()` |
+ | `usize` size-string parsing (`"25MiB"`) | Not directly supported by `arg-picker`; parse manually (e.g., via `size` directly or a custom `SinglePickable`) |
+ | `PathCheckRule` / `PathsChecker` / `PathChecker` | Use the new path wrapper types in `mingling::picker::value` (`FilePath`, `DirPath`, `NoPath`, `RecursiveFiles`, etc.) |
+ | `Yes` / `True` | Use `mingling::picker::value::Flag` (flag-based) and explicit value checks for confirmations |
+ - **Remove any `use mingling::parser::...;` imports.** All `parser` items are gone. Use the `picker` feature's module (`mingling::picker`, `arg_picker::prelude::arg`, etc.).
+ - **If you used the `usize` size-string parsing** (e.g., `preved.pick::<usize>("--size").unpack()` with inputs like `"25mib"`), implement a custom `SinglePickable` or use the `size` crate directly.
+ - **If you used `AsPicker`** on `Vec<String>` / `&[String]`, use `arg_picker::picker::IntoPicker` (or `EntryPicker`, for program entry types) instead.
+
+ _Behavioral note:_ the `picked` values, flag parsing semantics, and the "longest registered prefix wins" matching rule are all preserved by the `picker` feature's `arg-picker` API. The removal is purely a dead-code cleanup: the legacy built-in parser has been fully superseded by `picker`, and downstream code must migrate to the new API names described above.
+
+5. **[`macros`]** **[BREAKING REMOVAL]** Removed the `pack!`, `pack_err!`, `pack_structural!`, and `pack_err_structural!` macros and replaced all pipeline type definitions with `#[derive(Grouped)]` (and, where applicable, `#[derive(Grouped, Wrap)]`) on user-defined structs.
+
+ ### What changed
+
+ The `pack!` family of macros — which generated a wrapper struct with a fixed `inner` field and a suite of trait impls (`From`/`Into`, `AsRef`/`AsMut`, `Deref`/`DerefMut`, conditional `Default`, `Grouped`, and `Into<AnyOutput>`/`Into<ChainProcess>`) — has been removed. Pipeline types are now defined directly as ordinary Rust structs annotated with the `Grouped` derive macro, optionally combined with `Wrap` for the ergonomic trait impls previously provided by `pack!`.
+
+ **Removed API:**
+
+ - **`mingling::macros::pack!`** — Removed entirely (both the `pack`/`pack` and `pack`/`pack_structural` re-exports and the underlying macro implementations).
+ - **`mingling::macros::pack_err!`** — Removed (extras feature).
+ - **`mingling::macros::pack_structural!`** — Removed (structural_renderer feature).
+ - **`mingling::macros::pack_err_structural!`** — Removed (structural_renderer + extras features).
+ - _*All `pack`* re-exports_* from `mingling::prelude` and `mingling::macros` — removed.
+ - **`mingling_macros` implementation modules** — Deleted `func/pack.rs`, `func/pack_err.rs`, `func/pack_structural.rs`, `func/pack_err_structural.rs`, and `systems/structural_data.rs`.
+ - **`mingling_pathf::patterns::PackPattern`** — Removed from `pattern_analyzer::init()` and the `patterns` module (both the `patterns/pack.rs` file and its `pub use pack::*;` re-export).
+ - **Pathf test asset** — Deleted `mingling_pathf/test/src/test_files/test_pack.rs` and the `test_pack_analyze` test in `mingling_pathf/test/src/lib.rs`.
+ - **`mingling/src/docs/lib.md` / `mingling/src/example_docs.rs`** — The `example_pack_err` doc module (and the corresponding `examples/example-pack-err/` example) were removed (Cargo.toml, Cargo.lock, src/main.rs, page.toml, test.toml, and the `examples.json` entry).
+ - **`docs/pages/other/features.md`** — The `pack_err!` row and detailed section were removed in favor of a "Declaring Error Types" section documenting `#[derive(Grouped, Default)]` (unit form) / `#[derive(Grouped, Wrap)]` (typed form).
+ - **`docs/_zh_CN/pages/other/features.md`** — Same changes as the English `features.md`.
+ - **`mingling/src/gen_program.rs`** — Doc comments updated from "created by the `pack!` macro" to "generated by the `gen_program!` macro" for `Entry`, `ErrorRendererNotFound`, `EntryFallback`, and `CompletionContext`. The "You can register it using `with_dispatcher`" doc on `CMDCompletion` was removed (since `with_dispatcher` no longer exists). The "and many others" phrase in the `macros` module doc was trimmed to remove `pack!` and friends.
+
+ **Migration guide:**
+
+ | Old `pack!`-family macro usage | New equivalent |
+ | ------------------------------------------ | ----------------------------------------------------------------------------------------- |
+ | `pack!(TypeName = Inner);` | `#[derive(Grouped, Wrap)] pub struct TypeName(Inner);` |
+ | `pack!(TypeName = (A, B));` | `#[derive(Grouped, Wrap)] pub struct TypeName((A, B));` |
+ | `pack!(TypeName = ());` (unit) | `#[derive(Grouped, Wrap, Default)] pub struct TypeName(());` |
+ | `pack_err!(ErrorName);` (simple) | `#[derive(Grouped, Default)] pub struct ErrorName;` |
+ | `pack_err!(ErrorName = Inner);` (typed) | `#[derive(Grouped, Wrap)] pub struct ErrorName(Inner);` |
+ | `pack_structural!(TypeName = Inner);` | `#[derive(serde::Serialize, StructuralData, Grouped, Wrap)] pub struct TypeName(Inner);` |
+ | `pack_err_structural!(ErrorName);` | `#[derive(serde::Serialize, StructuralData, Grouped, Default)] pub struct ErrorName;` |
+ | `pack_err_structural!(ErrorName = Inner);` | `#[derive(serde::Serialize, StructuralData, Grouped, Wrap)] pub struct ErrorName(Inner);` |
+
+ Behavioral differences to note when migrating:
+
+ - **Field access** — The old `pack!` types exposed a `pub inner` field. The new `#[derive(Grouped, Wrap)]` types are tuple structs whose single field is accessed as `.0` (or `.0.0` / `.0.1` when the inner type is itself a tuple, e.g. `(String, String)`). All internal call sites and examples were updated accordingly (e.g. `StateConfigEdit((String, String))` now accesses `kv.0.0` / `kv.0.1`, `StatePkgEnable((String, String))` accesses `p.0.0` / `p.0.1`).
+ - **Constructor** — `TypeName::new(inner)` becomes `TypeName(inner)`. `Dispatcher::begin` now constructs the entry as `#pack(args)` instead of `#pack::new(args)`.
+ - **`name` field / `info` field** — `pack_err!`'s auto-generated `name: String` field (snake_cased at compile time) and `info: Type` field are gone. Unit errors use `#[derive(Grouped, Default)]` and are constructed as plain unit values (`ErrorFoo`), while typed errors wrap their payload as `.0` (accessed as `err.0` instead of `err.info`).
+ - **`Default`** — `pack!`'s `Default` was conditional on the inner type. The new `#[derive(Grouped, Wrap, Default)]` requires the inner type to also impl `Default`; for unit-like errors use `#[derive(Grouped, Default)]` (a plain unit struct).
+ - **`AsRef` / `AsMut`** — The `Wrap` derive generates `Deref`/`DerefMut`, `From`, and `Into` but not `AsRef`/`AsMut`. Code relying on `AsRef`/`AsMut` from `pack!` should switch to `Deref` (`*value`) or field access.
+ - **Structured output** — `pack_structural!` / `pack_err_structural!` auto-derived `serde::Serialize` and `StructuralData`. The new equivalent requires adding `#[derive(serde::Serialize, StructuralData, ...)]` explicitly (and `use mingling::StructuralData;` when needed).
+ - **`register_type!`** — Both `Grouped` and `Wrap` derives invoke `register_type!` internally, so no separate registration call is needed.
+
+ **Examples of internal updates in this release** (all files updated from `pack!`/`pack_err!`/etc. to the derive-based form): `examples/example-argument-picker`, `examples/example-async-support`, `examples/example-basic`, `examples/example-combine-pathf-dispatch-tree`, `examples/example-combine-pathf-metadata`, `examples/example-command-macro`, `examples/example-completion`, `examples/example-error-handling`, `examples/example-exitcode`, `examples/example-hook`, `examples/example-lazy-resources`, `examples/example-metadata`, `examples/example-outside-type`, `examples/example-panic-unwind`, `examples/example-pathfinder`, `examples/example-repl-basic`, `examples/example-setup`, `examples/example-structural-renderer`, `examples/example-unit-test`, `examples/full-todolist`, `mingling_cli/src/config/cmd_cfg.rs`, `mingling_cli/src/lib.rs`, `mingling_cli/src/linter/cmd_explain.rs`, `mingling_cli/src/linter/cmd_lint.rs`, `mingling_cli/src/linter/mlint_report.rs`, `mingling_cli/src/metadata/cmd_metadata.rs`, `mingling_cli/src/pkg_mgr.rs`, `mingling_cli/src/pkg_mgr/cmd_install.rs`, `mingling_cli/src/pkg_mgr/cmd_internal_loadpkgs.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_disable.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_enable.rs`, `mingling_cli/src/pkg_mgr/cmd_pkg_show.rs`, `mingling_cli/src/pkg_mgr/cmd_uninstall.rs`, `mingling_cli/src/proj_mgr/cmd_class_add.rs`, `mingling_cli/src/proj_mgr/cmd_proj_init.rs`, the `dispatcher!` / `entry!` / `gen_program!` / `program_comp_gen!` / `program_fallback_gen!` / `program_final_gen!` macro implementations, `mingling_macros/src/func/dispatcher_clap.rs`, `mingling/src/example_docs.rs`, `mingling/src/docs/lib.md`, `README.md`, `GETTING-STARTED.md`, and all docs pages / tests listed above.
+
+ _Behavioral note:_ the runtime semantics of pipeline types are unchanged — `#[derive(Grouped, Wrap)]` produces types with the same `Grouped` identity, `Into<AnyOutput>`/`Into<ChainProcess>` routing, `Deref`/`DerefMut`, and `From`/`Into` conversions that `pack!` provided. The removal is purely an API move from magic macros to standard Rust derives, reducing macro surface area and making pipeline types inspectable and composable like any other struct.
+
+6. **[`macros:completion`]** **[BREAKING]** Changed the `#[completion]` attribute macro's context-parameter semantics: completion functions now take the **owned** `ShellContext` (or any `From<&ShellContext>` type) by value, and `&ShellContext` is no longer accepted.
+
+ ### What changed
+
+ Previously, the completion function's context parameter could be `&ShellContext` (the classic form) or an owned `ShellContext` / any `From<&ShellContext>` type. Now the reference form is rejected: reference parameters (`&T` / `&mut T`) are reserved exclusively for **resource injection**, matching `#[chain]` semantics, so the parser in `mingling_macros/src/attr/completion.rs` was reworked to classify each parameter as either:
+
+ - **Owned (non-reference) parameter** — a _shell source_: derived from `&ShellContext` via `<#ty as From<&ShellContext>>::from(ctx)`. This covers `ShellContext` itself (via its new `Clone`-based `From` impl), framework state types, and any user-defined type derived from the shell context. Multiple owned parameters are allowed; each gets its own derived binding (`__ctx_derived_{idx}`).
+ - **`&T` / `&mut T` reference parameter** — a _resource injection_, identical to the parameter position used by `#[chain]`. Requires a simple-identifier binding. `&ShellContext` specifically is rejected with a compile error: "`&ShellContext` is not supported; use the owned `ShellContext` (or any other type implementing `From<&ShellContext>`) as a value parameter".
+
+ A helper `is_shell_context_path(ty)` detects a path whose last segment is `ShellContext` (covering `ShellContext` and `mingling::ShellContext` alike).
+
+ Previously, resource injection only started _after_ the first (context) parameter, and a completion function with no context parameter could not inject resources (compile error). Now, ownership of the parameter — not its position — determines its role: owned parameters are shell sources, references are resources, and they may be freely interleaved. The "no context → no resources" restriction is gone entirely.
+
+ The generated `Completion::comp` body now emits:
+
+ 1. A derived-binding statement for each owned parameter.
+ 2. The immut-resource binding statements (for `&T` injections).
+ 3. The mut-resource wrapper / call (for `&mut T` injections).
+ 4. The return statement applying the `Into<Suggest>` conversion (`()` → empty `Suggest`).
+
+ **Migration guide:**
+
+ - Change every `ctx: &ShellContext` parameter to `ctx: ShellContext`. The owned type behaves identically for reads; only the declared parameter type changes.
+ - Code that previously relied on `&ShellContext` in the _middle_ of the signature no longer needs special treatment: owned parameters anywhere are treated as shell sources.
+ - `_ctx: &ShellContext` (unused parameter) becomes `_ctx: ShellContext`.
+
+ _All internal call sites, examples, docs, and tests updated_ (e.g., `mingling_cli` completion handlers, `example-completion`, `example-enum-tag`, `GETTING-STARTED.md`, `docs/pages/advanced/1-completion.md`, `docs/_zh_CN/pages/advanced/1-completion.md`, and `mingling/src/example_docs.rs`).
+
+7. **[`build`]** **[BREAKING]** Replaced the `build` / `builds` build-time feature system with compile-time macro-driven build steps. The `build` feature, `builds` feature, `build_advanced` preset, `build_full` preset, `mingling::build` module, and the entire `build.rs`-based workflow have been removed. Build steps (completion script generation and pathf type-mapping analysis) now run automatically as a side effect of `gen_program!()` expansion via new `build_comp!()` / `build_pathf!()` macros.
+
+ ### What changed
+
+ Previously, build-time functionality was enabled through the `build` feature (and preset groups `build_advanced` / `build_full`, plus the deprecated `builds` alias). Users needed a `[build-dependencies.mingling]` entry in `Cargo.toml` and a hand-written `build.rs` that called `mingling::build::build_comp_scripts(...)` (for completion scripts) and `mingling::build::analyze_and_build_type_mapping()` (for pathf). These functions were gated behind the `build` + `comp` / `build` + `pathf` feature combinations and read `OUT_DIR` to locate the output directory.
+
+ Now, the build steps are integrated directly into macro expansion:
+
+ - **`gen_program!()` automatically invokes `build_comp!()`** (when the `comp` feature is enabled) and **`build_pathf!()`** (when the `pathf` feature is enabled) at the very start of its expansion. These macros run the build logic as a compile-time side effect and expand to nothing.
+ - **`build_comp!()`** is a proc macro (re-exported as `mingling::macros::build_comp`) that generates completion scripts into `{target_directory}/mingling/`. It accepts an optional string literal for the binary name; without an argument it defaults to `CARGO_PKG_NAME`. On failure it emits a `compile_error!`.
+ - **`build_pathf!()`** is a proc macro (re-exported as `mingling::macros::build_pathf`) that runs the pathf type-mapping analysis, writing mapping files into `{target_directory}/mingling/{CARGO_PKG_NAME}/`. On failure it emits a `compile_error!`.
+ - **No `build.rs` is required anymore.** Build logic runs from proc-macro expansion, so no `[build-dependencies.mingling]` entry, no `build` feature, and no `build.rs` file are needed.
+
+ **Removed API:**
+
+ - **`build` feature** — Removed from `mingling/Cargo.toml` and `mingling_core/Cargo.toml`. The `build = ["mingling_core/build"]` feature mapping and the `mingling_core/build` feature have been deleted.
+ - **`builds` feature** — Removed (deprecated alias, mapped to `mingling_core/build`).
+ - **`build_advanced` / `build_full` preset features** — Removed from `mingling/Cargo.toml` feature groups.
+ - **`mingling::build` module** — Removed entirely from `mingling_core`:
+ - `mingling_core/src/build.rs` and the `mingling_core/src/build/` directory deleted.
+ - `mingling_core/src/docs/build.md` deleted.
+ - `mingling_core/src/lib.rs` no longer gates `pub mod build` behind the `build` feature.
+ - **Build functions** — Removed: `build_comp_scripts`, `build_comp_script`, `build_comp_script_to`, `build_comp_script_to_file`, `analyze_and_build_type_mapping`, `analyze_and_build_type_mapping_for`, `analyze`.
+ - **`MINGLING_BUILD` / `MINGLING_BUILDS` / `MINGLING_BUILD_ADVANCED` / `MINGLING_BUILD_FULL` feature constants** — Removed from `mingling/src/features.rs`.
+ - **`mingling_core` dependencies** — Removed `just_template` (comp) and `mingling_pathf` (pathf) from `mingling_core/Cargo.toml`; these moved to `mingling_macros` as optional dependencies gated behind the `comp` / `pathf` features.
+ - **`mingling_macros` feature wiring** — Changed `comp = []` to `comp = ["dep:just_template", "dep:mingling_pathf"]` and `pathf = []` to `pathf = ["dep:mingling_pathf"]`; `mingling` crate's `pathf` feature no longer forwards to `mingling_core/pathf`.
+ - **`mingling::build::pathf` error re-exports** — `mingling_core::error` no longer re-exports `mingling_pathf::error::*`.
+
+ **New internal infrastructure:**
+
+ - **`mingling_macros/src/build.rs`** — New module hosting `comp_build_impl` (behind `comp`) and `pathf_build_impl` (behind `pathf`), which parse the macro input and delegate to the build logic, converting errors into `compile_error!` token streams.
+ - **`mingling_macros/src/build/comp.rs`** — Moved from `mingling_core/src/build/comp.rs` (with the shell templates, which moved from `mingling_core/tmpls/comps/` to `mingling_macros/tmpls/comps/`). Contains a private copy of `ShellFlag` (since the macros crate cannot depend on `mingling_core`); the template files are identical. Scripts are written to `{target_directory}/mingling/` resolved via the new `mingling_pathf::build_output_dir()`.
+ - **`mingling_macros/src/build/pathf.rs`** — New module providing `output_dir()` (`{target_directory}/mingling/{CARGO_PKG_NAME}`) and `analyze_and_build_type_mapping()` delegating to `mingling_pathf`.
+ - **`mingling_pathf::build_output_dir()`** — New public function resolving `{target_directory}/mingling/` via `cargo metadata` (from `CARGO_MANIFEST_DIR`).
+ - **`mingling_pathf::target_directory()`** — New public function running `cargo metadata` (`no_deps`) from a crate directory and returning the target directory.
+ - **`MinglingPathfinderError::CargoMetadata(String)`** — New error variant added to `mingling_pathf`'s error enum.
+ - **`cargo_metadata` dependency** — Added to `mingling_pathf` (workspace, version `0.23.1`) and to the root workspace `Cargo.toml`.
+
+ **`gen_program!()` changes** (`mingling_macros/src/func/gen_program.rs`):
+ - Emits `::mingling::macros::build_comp!();` at the start of the expansion when `comp` is enabled (and `::mingling::macros::build_pathf!();` when `pathf` is enabled).
+ - The pathf `use`-statement loading now runs the analysis inline via `crate::build::pathf::analyze_and_build_type_mapping()` (so the mapping exists when the `use` statements are read) and loads `type_using.rs` from `crate::build::pathf::output_dir()`.
+ - The `load_pathf_uses` function now reads from `{target_directory}/mingling/{CARGO_PKG_NAME}/type_using.rs` instead of `{OUT_DIR}/{CARGO_PKG_NAME}/type_using.rs`.
+ - The empty-uses `compile_error!` hint was reworded: it no longer mentions `build.rs` or the `build` feature; it now says the analyzer found no types and suggests ensuring the `pathf` feature is enabled and `gen_program!()` is called in a crate with a `src/` directory.
+ - `mingling_pathf::analyze_and_build_type_mapping` no longer emits `cargo:rerun-if-changed=src/` / `cargo:rerun-if-env-changed=...` directives (there is no build script for Cargo to track).
+
+ **Migration guide:**
+
+ - **Delete `build.rs`** (and the `[build-dependencies]` block in `Cargo.toml`). Completion scripts are generated automatically when the `comp` feature is enabled; pathf analysis and any `[build-dependencies.mingling]`). If a feature list references only these, delete the whole section.
+ - **If your binary name differs from the crate name**, call `build_comp!()` manually with the binary name:
+ ```rust
+ // Features: ["comp"]
+ mingling::macros::build_comp!("mybin");
+ ```
+ This can be placed at module scope (e.g., in `src/lib.rs` or `src/main.rs`) alongside `gen_program!()`.
+ - **Remove any `mingling::build::...` imports.**
+ - **Example/build artifacts**: The completion scripts are now written to `{target_directory}/mingling/` rather than `{target_directory}/release/` or the `OUT_DIR`-derived path. Any scripts that copied them from the release directory must be updated (e.g., `.run/src/bin/install-mling.sh` now copies from `.temp/target/mingling/mling_comp.$comp`, and `.run/src/bin/install-mling.ps1` from `.temp/target/mingling/mling_comp.ps1`).
+ - **`mingling_cli`**: `build.rs` no longer calls `analyze_and_build_type_mapping` / `build_comp_scripts`; `mingling_cli/src/lib.rs` now invokes `mingling::macros::build_comp!("mling")` to generate scripts for the `mling` binary. `StateInstallBuild` / `StateInstallCopy` gained a `mingling_dir` field (`{target}/mingling/`) and the install copy step reads completion scripts from `{target}/mingling/` instead of `{target}/release/`.
+ - **Tests/examples**: Removed the `builds` feature from `mingling_core/tests/test-all`, `mingling_core/tests/test-comp`, and all pathf/completion examples, and deleted the corresponding `build.rs` files and `[build-dependencies]` blocks.
+
+ _Behavioral note:_ the runtime behavior of programs is unchanged — completion scripts and pathf type mappings are still produced, just from compile-time macro expansion instead of a separate `build.rs` step. The output directory changed from an `OUT_DIR`-derived path (effectively `{target}/<profile>` style) to a dedicated `{target_directory}/mingling/` directory resolved via `cargo metadata`, which is deterministic regardless of build profile.
+
+8. **[`Cargo.toml`]** Removed the legacy `extra_macros` feature alias from `mingling/Cargo.toml`. The `extras` feature (introduced in 0.4.0, BREAKING CHANGE #1) is now the sole name for this feature; the deprecated alias is gone.
+
+9. **[`setups:dirs`]** **[BREAKING]** Simplified the `DirectoryEnvironmentSetup` type — it is no longer generic over the program collect type `C` and no longer requires `DirectoryEnvironmentSetup::<C>::default()` to construct.
+
+ ### What changed
+
+ Previously, `DirectoryEnvironmentSetup` was a generic struct `DirectoryEnvironmentSetup<C>` (carrying `PhantomData<C>`) that had to be constructed via `DirectoryEnvironmentSetup::<C>::default()` before calling `Program::with_setup`. Now the struct is unit-like (`pub struct DirectoryEnvironmentSetup;`), so it can be constructed directly as a value with no `::default()` call and no generic parameter.
+
+ Additionally, the `setup` method's `program` parameter was retyped from `crate::Program<C>` to `mingling_core::Program<C>` for cleanliness.
+
+ ### Removed / changed API
+ - **`DirectoryEnvironmentSetup<C>`** → **`DirectoryEnvironmentSetup`** — The struct no longer has a generic parameter (previously `DirectoryEnvironmentSetup<C>` with `PhantomData<C>`).
+ - **`impl<C> Default for DirectoryEnvironmentSetup<C>`** — Removed. The unit struct uses the derived/implicit `Default`, and more importantly construction is now just the plain value `DirectoryEnvironmentSetup`, not `DirectoryEnvironmentSetup::<C>::default()`.
+ - **`impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup<C>`** → **`impl<C> ProgramSetup<C> for DirectoryEnvironmentSetup`** — The `ProgramSetup` impl is now on the unit type.
+
+ ### Migration guide
+ - Replace `program.with_setup(DirectoryEnvironmentSetup::<ThisProgram>::default())` with `program.with_setup(DirectoryEnvironmentSetup)`.
+ - Any type annotations referencing `DirectoryEnvironmentSetup<C>` must drop the generic argument.
+
+ _No behavioral changes — the setup still registers the same four directory resources (`ResCurrentDir`, `ResCurrentExe`, `ResHomeDir`, `ResTempDir`) in the program's resource store. The type simplification is purely ergonomic.
+
+10. **[`setups:exit_code`]** **[BREAKING]** Simplified the `ExitCodeSetup` type — it is no longer generic over the program collect type `C` and no longer requires `ExitCodeSetup::<C>::default()` to construct.
+
+ ### What changed
+
+ Previously, `ExitCodeSetup` was a generic struct `ExitCodeSetup<C>` (carrying `PhantomData<C>`) that had to be constructed via `ExitCodeSetup::<C>::default()` before calling `Program::with_setup`. Now the struct is unit-like (`pub struct ExitCodeSetup;`), so it can be constructed directly as a value with no `::default()` call and no generic parameter.
+
+ Additionally, the `setup` method's `program` parameter was retyped from `crate::Program<C>` to `mingling_core::Program<C>` for cleanliness.
+
+ ### Removed / changed API
+ - **`ExitCodeSetup<C>`** → **`ExitCodeSetup`** — The struct no longer has a generic parameter (previously `ExitCodeSetup<C>` with `PhantomData<C>`).
+ - **`impl<C> Default for ExitCodeSetup<C>`** — Removed. The unit struct uses the derived/implicit `Default`, and more importantly construction is now just the plain value `ExitCodeSetup`, not `ExitCodeSetup::<C>::default()`.
+ - **`impl<C> ProgramSetup<C> for ExitCodeSetup<C>`** → **`impl<C> ProgramSetup<C> for ExitCodeSetup`** — The `ProgramSetup` impl is now on the unit type.
+
+ ### Migration guide
+ - Replace `program.with_setup(ExitCodeSetup::<ThisProgram>::default())` (or `ExitCodeSetup::default()`) with `program.with_setup(ExitCodeSetup)`.
+ - Any type annotations referencing `ExitCodeSetup<C>` must drop the generic argument.
+
+ _No behavioral changes — the setup still registers the same `ResExitCode` resource (initialised to `0`) and installs the same program-finish hook that overrides the program's exit code when the resource holds a non-zero value. The type simplification is purely ergonomic._
+
+---
+
+## Contents
+
+### 0.4.0 (2026-08-16)
+
+#### Fixes:
+
+1. **[`comps:zsh`]** Fixed zsh completion script to properly escape colons in completion descriptions. The zsh completion script generated for the `zsh` output format now escapes colon characters in completion items (`${item//:/\\:}`) and description parts (`${match[1]//:/\\:}`) so that descriptions containing colons don't break the `_describe` command's parsing. Additionally, fixed the simple-completions branch to iterate over the original `completions` array (with the colon-escaped format matching) rather than the already-parsed `parsed_completions` array, correctly extracting the completion item when no description is present.
+
+2. **[`comps:bash`]** Reworked the bash completion script template to fix word-index tracking when the cursor is in the middle of a word and to add colon-ltrimming for completion descriptions. The script now computes the current word (`cur`) using `COMP_LINE` truncated to `COMP_POINT` (taking the last whitespace-delimited token) rather than relying on `COMP_WORDS[COMP_CWORD]`, which fails when the cursor is in the middle of a word. The word index is derived from the count of words preceding the current cursor position (`before_words`), and `prev` is the last word in that preceding set. The option flags to the underlying completion engine are now passed as `-f value`/`-C value`/etc. (space-separated) instead of `-f=value`/`-C=value`/etc. (equals-separated), since the engine expects space-delimited argument pairs. Additionally, when the current word contains a colon and `COMP_WORDBREAKS` includes `:`, the completion items are trimmed of the colon prefix before being inserted into `COMPREPLY` — this prevents completions like `foo:bar` from being double-prefixed with the literal `foo:bar` when bash would otherwise append the full word.
+
+3. **[`core:comp`]** Fixed the completion engine to correctly resolve command nodes when global parameters (flags and their values) precede the subcommand. Previously, the engine matched the command tree starting from the first argument after the program name, treating every leading argument as part of the command path — so `prog [PARAM]... <subcommand>` style invocations would fail to match any node. Now:
+
+ - A new helper `first_command_arg_index::<P>(args)` scans the argument list and returns the index of the first argument that matches the head of a registered command node (excluding node names starting with `_`). Everything before that index is treated as global parameters and skipped during command tree matching.
+ - In `CompletionHelper::complete`, the dispatch args are sliced to start at the first command-node match (`all_args[start..]`); if no node match is found, the args are empty (`Vec::new()`).
+ - In `default_completion`, the input path resolution skips the leading global-parameter arguments the same way, so `prog -v hello` correctly suggests the `hello` node even though `-v` precedes it.
+ - In the unmatched-dispatcher branch: when a command node _has_ been matched (`first_cmd_match.is_some()`), the `EntryFallback` handler is **skipped** and only `default_completion` runs, since global parameters do not warrant invoking the fallback. When no node was matched, the previous behavior is retained (fallback combined with default completion).
+
+ This enables `prog [PARAM]... <subcommand>` style invocations to resolve the subcommand correctly, while a "broken" path such as `prog -v hello -a someone` still fails to match a `hello someone` node (since `-a someone` lies _after_ the first matched node and participates normally).
+
+4. **[`core:comp`]** Added `add_prefix()` and `add_suffix()` methods to `Suggest` for batch-transforming suggestion text:
+
+ - **`add_prefix(self, prefix: impl Into<String>) -> Suggest`** — Takes the current `Suggest` value and prepends the given prefix to the suggestion text of every item. If the `Suggest` value is `Suggest::FileCompletion`, it is returned unchanged. For example, `["foo", "bar"]` with prefix `"--"` becomes `["--foo", "--bar"]`.
+ - **`add_suffix(self, suffix: impl Into<String>) -> Suggest`** — Takes the current `Suggest` value and appends the given suffix to the suggestion text of every item. If the `Suggest` value is `Suggest::FileCompletion`, it is returned unchanged. For example, `["foo", "bar"]` with suffix `"="` becomes `["foo=", "bar="]`.
+
+ Both methods consume the original `Suggest` value and return a new one, enabling ergonomic chaining with the existing `combine()` method for transforming completion suggestion sets.
+
+5. **[`core:dispatch_tree`]** Refactored `build_dispatch_body` in `mingling_macros/src/systems/dispatch_tree_gen.rs` to use a caller-provided `no_match` fallback token stream instead of hardcoding `return Ok(Self::build_entry_fallback(raw.to_vec()));` at every level of the generated trie.
+
+ - The root call from `gen_dispatch_args_trie` passes the current fallback token stream as `no_match`.
+ - The `no_match` parameter is threaded down through recursive calls; when a subtree has no nodes, it returns the caller's `no_match` code instead of unconditionally emitting the fallback.
+ - For each node, `level_no_match` is computed as the exact-endpoint checks for that node followed by the caller's `no_match` code. This ensures exact endpoints at the current level are tried before giving up and bubbling up to the parent.
+ - When a node has children, the arm for each child runs the child's body first; if the child subtree fails to match, control falls through to `level_no_match` (exact endpoints here, then the parent's fallback), so longer registered paths are preferred over the exact endpoint at the same depth.
+ - When a node has no children, the generated code runs the exact-endpoint checks followed by `no_match`.
+
+ Behavioral result: the static trie dispatcher now follows the same "longest registered prefix wins" rule as the dynamic dispatcher — a child (longer) path is preferred over an exact endpoint at the same depth, and only when every descendant fails to match is the exact endpoint at the current depth dispatched.
+
+6. **[`core:comp`]** Changed the `CompletionHelper::complete` method so that when a concrete entry is dispatched (i.e., a custom completion handler produces a `Suggest` value), the custom completion is now **merged with the default completion suggestions** rather than replacing them entirely.
+
+ Previously, when an explicit completion handler produced a `Suggest`, that value was returned as-is and the default subcommand suggestions (leaf nodes under the current path) were never consulted. Now:
+
+ - The default completion (`default_completion::<P>(ctx)`) is always computed and merged via `Suggest::combine()` when the custom suggestion is not `Suggest::FileCompletion`.
+ - If the custom completion is `Suggest::FileCompletion`, the default completion is used instead (since `FileCompletion` cannot be meaningfully merged with subcommand suggestions).
+ - Concrete entry completions and default subcommand suggestions coexist — e.g., `thanks <tab>` now suggests both the leaf nodes (`bob`, `alice`) and the `thanks` entry's own completion.
+
+7. **[`core:render`]** Fixed the `RenderResult::eprintln` method to actually write to stderr. Previously, this method shamefully used `println!` (which writes to stdout) when `immediate_output` was enabled, rather than `eprintln!` (which writes to stderr). Yes, you read that right — a method literally named `eprintln` was printing to stdout. Talk about a identity crisis. The output has been corrected to use `eprintln!`, ensuring that error-level render output is properly separated from standard output streams. Whoever wrote that deserves a wet noodle slap — the entire point of an `e`-prefixed method is that it goes to standard _error_, not standard _out_. At least the bug is dead now, and we can all sleep a little easier knowing "error" output goes where error output belongs.
+
+8. **[`pathf`]** Removed the `BasicStructPattern` from the pathf pattern analyzer. This pattern previously matched arbitrary structs (in the `BasicStruct` sense) and would attempt to treat plain structs as pathf-analyzable items. However, `BasicStructPattern` produced no meaningful analysis — it matched plain structs that weren't associated with any Mingling macro (like `#[chain]`, `#[group]`, etc.), so removing it eliminates irrelevant `AnalyzeItem` entries and reduces noise in the generated `type_using.rs`.
+
+ Specifically:
+ - Removed `analyzer.add_pattern(BasicStructPattern)` from `init_with_config` in `pattern_analyzer.rs`.
+ - Removed the `basic_struct` module and its re-export (`pub use basic_struct::*;`) from `patterns.rs`.
+ - Updated the pathf integration test (`test_pattern_analyzer_once`) to assert that plain structs nested in submodules are **no longer** collected: `assert!(!result.contains("::directly_sub_mod::DirectlySubModStruct"))`.
+
+ Structs that are analyzed by other patterns (e.g., `GroupedDerivePattern`, `ChainPattern`, etc.) continue to work exactly as before — only the standalone "bare struct with no macro association" detection has been removed.
+
+9. **[`comps:pwsh`]** Fixed and refactored the PowerShell completion script template in `mingling_core/tmpls/comps/pwsh.ps1`:
+
+ - **Option value passing** — Changed from the compact `"-f", ($line -replace '-', '^')` array-literal style to conditionally appended `$args += "-f"` / `$args += ($line -replace '-', '^')` pairs for each of `-f`, `-w`, `-p`, and `-c`. This ensures empty values are not passed to the completion engine, preventing spurious argument parsing.
+ - **Word index fix** — `$wordIndex` is now `$i + 1` (when the current word is found in the element list) and `$elements.Count + 1` (when not found), correcting off-by-one indexing. Since the elements array starts at index 0, the actual 1-based word index that the completion engine expects is the array index plus one.
+ - **Empty element filtering** — The `-a` argument loop now skips empty elements (`if ($element)`) so blank entries in the element list are not passed as standalone `-a` arguments with empty values.
+ - **Non-flag optional arguments** — The `-C` (cursor position) and `-i` (word index) parameters remain unconditionally passed, while `-f`, `-w`, `-p`, `-c`, and `-a` are only emitted when their corresponding values are non-empty.
+
+ These changes fix incorrect word-index computation and avoid sending empty option values to the underlying completion engine from PowerShell completions.
+
+10. **[`core:resources`]** Refactored the global resource storage from a single type-erased `Arc<Mutex<HashMap<...>>>` container into a standalone `GlobalResContainer` struct with per-resource mutexes.
+
+ Previously, `GlobalResources` was a type alias for `Arc<Mutex<HashMap<TypeId, Box<dyn Any + Sync + Send>>>>`, and the resource methods (`with_resource`, `modify_res`, `__modify_res_and_return_route`, `__extract_res_mut`, `__store_res`, `res`, `res_or_route`, `res_or_default`) were implemented directly on `Program`. Now:
+
+ - **`GlobalResContainer`** — A new public struct owning the resource map (`map: Mutex<HashMap<TypeId, Box<dyn Any + Send + Sync>>>`), with `new()` and `Default` impls. Each resource entry is stored as `Arc<Mutex<Arc<Res>>>` — the outer `Mutex` guards the entry itself (so a resource can be locked without holding the container lock), and the inner `Arc<Res>` is the shared immutable snapshot returned by `res()`.
+ - **Per-resource mutex** — The container lock is only held for the brief lookup/clone of the entry. This means two nested `modify_res` calls (e.g., two `&mut` resource parameters generated by `#[chain]`) lock _different_ mutexes and cannot deadlock against each other.
+ - **`Program` delegation** — All resource methods on `Program` now delegate to its internal `resources: GlobalResContainer` member, keeping the public `Program` resource API unchanged.
+ - **Generic helper methods** — `__modify_res_and_return_route<Res, C>` and `res_or_route<Res, C>` now take an explicit `C: ProgramCollect<Enum = C>` generic parameter (added separately on the container), with `Program`'s versions keeping the single-`Res` signature.
+ - **`__store_res` semantics** — If an entry already exists for the type and is a matching `Arc<Mutex<Arc<Res>>>`, the value is updated in place under the entry lock; otherwise (missing entry, type mismatch, or poisoned lock) the entry is replaced wholesale.
+
+ The container is standalone and not coupled to any `Program` instance or the global `this::<C>()` context, so any number of independent `GlobalResContainer`s can be created and used simultaneously.
+
+ _No behavioral changes for downstream code — `Program`'s public resource API (`with_resource`, `modify_res`, `res`, `res_or_route`, `res_or_default`) is unchanged, and the `&mut` resource injection syntax for `#[chain]` (sync and async) continues to work as before. A side benefit: nested resource modifications no longer risk deadlock._
+
+#### Optimizations:
+
+1. **[`pathf`]** Added `is_module` field to `AnalyzeItem` and a new constructor `AnalyzeItem::local_module(module, item_name)` which sets `is_module: true`. The `type_mapping_builder` now tracks whether an item is a module: when generating `type_using.rs`, module items produce `use path::to::module::*;` (glob import) instead of the standard `use path::to::TypeName;` direct import. Non-module items continue to use direct imports as before. The internal data structure changed from `Vec<(String, String)>` to `Vec<(String, String, bool)>` to carry the `is_module` flag through the pipeline.
+
+2. **[`macros:gen_program`]** Wrapped all code generated by `gen_program!()` inside a `__this_program_impl` module and re-exported it with `pub use __this_program_impl::*;`. This isolates the generated internal items (type aliases, trait implementations, and pathf-generated `use` statements) from the call site's module namespace, preventing name collisions and keeping generated machinery out of the caller's direct scope.
+
+ - The `Next` type alias, `Routable` impl for `ChainProcess<ThisProgram>`, and the `program_fallback_gen!()` / `program_final_gen!()` expansions are now all inside `pub mod __this_program_impl { ... }`, then re-exported publicly.
+ - Pathf integration: when the `pathf` feature is enabled, the `type_using.rs` file (generated by the build script) is loaded at compile time via `load_pathf_uses()` and emitted as `use ...;` statements **inside** the `__this_program_impl` module. Previously, pathf uses were injected via `include!()` inside the `ProgramCollect` impl block in `program_final_gen`; now they are loaded by `gen_program` itself and placed at the top of the hidden module. A `compile_error!` hint is emitted if the pathf file is missing or empty.
+ - When `pathf` is **disabled**, `__this_program_impl` emits `use super::*;` to bring the caller's parent scope types into the generated module, preserving existing behavior for projects that don't use pathf.
+ - Completion generation: removed `crate::` prefix from `CompletionSuggest` references in `program_comp_gen.rs`, since the generated code now lives inside `__this_program_impl` and no longer has a direct `crate` path to the user's crate root. The prefix became unnecessary because `CompletionSuggest` is expected to be in scope (e.g., via pathf glob re-exports or the `use super::*;` fallback).
+
+ _No behavioral change for downstream code — all public items are re-exported with the same names. The `__this_program_impl` module is `#[doc(hidden)]` and not part of the public API._
+
+3. **[`core:comp`]** Added `add_suggest()` and `add_suggest_with_description()` methods to `Suggest` for batch-adding suggestion items:
+
+ - **`add_suggest(&mut self, items: impl Into<Vec<String>>)`** — Wraps each item in `SuggestItem::Simple` and inserts it into the underlying `BTreeSet`.
+ - **`add_suggest_with_description(&mut self, items: impl Into<Vec<String>>, desc: impl Into<String>)`** — Wraps each item in `SuggestItem::WithDescription` using the provided description and inserts it into the set.
+
+ These methods enable ergonomic batch population of suggestion sets from collections of strings, complementing the existing `insert()` method.
+
+4. **[`macros:gen_program`]** Added a `CRATE_ROOT` module that is only visible when the `docs_rs` feature is enabled. This module exists purely for docs.rs documentation purposes — it provides a placeholder view of the structures, enums, and other items that `gen_program!()` generates into `crate::*`, allowing users to inspect and understand the behavior behind `gen_program!()` through generated documentation.
+
+ When the `docs_rs` feature is enabled, `gen_program!()` emits a `#[doc(hidden)]` `CRATE_ROOT` module (with the hidden attribute removed when `docs_rs` is active) containing doc comments that describe the generated items and their relationships. This gives users browsing docs.rs a clear picture of what `gen_program!` expands to — the `ThisProgram` type alias, the `Enum` enum, the `Entry` pack type, chain process types, and related plumbing — without needing to manually trace macro expansions.
+
+ When `docs_rs` is disabled (the default), the `CRATE_ROOT` module is not emitted at all, so there is zero impact on normal builds, generated code size, or compilation times.
+
+#### Features:
+
+1. **[`picker:value:paths`]** Added new path wrapper types to `arg_picker::value` for filesystem-aware argument parsing:
+
+ - **`FilePath`** — Wraps `PathBuf`, validated at parse time to exist and be a file.
+ - **`NoFilePath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a file.
+ - **`DirPath`** — Wraps `PathBuf`, validated at parse time to exist and be a directory.
+ - **`NoDirPath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a directory.
+ - **`SymlinkPath`** — Wraps `PathBuf`, validated at parse time to exist and be a symlink.
+ - **`NoSymlinkPath`** — Wraps `PathBuf`, validated at parse time to _not_ exist as a symlink.
+ - **`NoPath`** — Wraps `PathBuf`, validated at parse time to have no filesystem entry at all.
+ - **`RecursiveFiles`** — Wraps `Vec<PathBuf>`. If given a file path, returns a single-element list; if given a directory path, recursively collects all files (and symlinks) under it.
+
+ All single-path types implement `From<PathBuf>`, `From<&PathBuf>`, `AsRef<Path>`, `Deref<Target = PathBuf>`, `DerefMut`, and `Into<PathBuf>`. `RecursiveFiles` additionally provides `len()`, `is_empty()`, `iter()`, `From<Vec<RecursiveFiles>>` for merging multiple collections, and the `IntoRecursiveFiles` trait for ergonomic combination from `Vec<T>`, `&[T]`, and `[T; N]`.
+
+ Each type implements `SinglePickable`, performing filesystem validation at parse time and returning `NotFound` when the precondition is not met.
+
+2. **[`picker:parsing`]** Added convenience methods to the internal `repeat!`-generated tuple implementations for `PickArgParsed<T1, T2, ...>` structs in `arg_picker::picker::parse`:
+
+ - **`unwrap_or_default(self)`** — Returns the parsed values, using `Default::default()` for any missing required arguments. Panics if a route was selected.
+ - **`unwrap_or_else<F>(self, op: F)`** — Returns the parsed values, using the provided closure to generate default values for any missing required arguments. Panics if a route was selected.
+ - **`expect(self, msg: &str)`** — Returns the parsed values, or panics with the given message if a route was selected. Requires `Route: std::fmt::Debug`.
+
+ These methods provide ergonomic alternatives to `to_result()` + `unwrap()` / `unwrap_or_default()` / `unwrap_or_else()` / `expect()` chaining, reducing boilerplate when working with `PickArgParsed` tuples directly.
+
+3. **[`macros:command`]** Added the `#[command]` attribute macro (feature-gated behind `extras`) that converts a plain function with a `Vec<String>` parameter into a fully wired Mingling command. The macro:
+
+ - Calls `dispatcher!("command_name")` to register the dispatcher entry.
+ - Generates a `#[chain]` wrapper that bridges the entry type (`Entry{Pascal}`) to the original function.
+ - Preserves the original function unchanged (including attributes, extensions, visibility, and asyncness).
+
+ **Syntax variants:**
+
+ ```rust,ignore
+ // Simple form — auto-derives names from function name
+ #[command]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("greet"), CMDGreet, EntryGreet
+
+ // Explicit node path
+ #[command(node = "hello.world")]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("hello.world", CMDGreet => EntryGreet)
+
+ // Explicit name/entry overrides
+ #[command(name = MyDispatcher, entry = MyEntry)]
+ fn greet(args: Vec<String>) -> Next { /* ... */ }
+ // → dispatcher!("greet", MyDispatcher => MyEntry)
+ ```
+
+ **Extension attributes** (e.g. `buffer`, `routeify`) passed as bare paths in `#[command(...)]` are applied as `#[ext]` attributes **on the original function**, not on the generated chain wrapper. The chain wrapper always uses bare `#[::mingling::macros::chain]`.
+
+ **Resource injection:** Parameters after the first are treated as resource injections and passed through to the generated `#[chain]` wrapper unchanged.
+
+ **Hidden module:** Each `#[command]` generates a `#[doc(hidden)]` module `__command_{fn_name}_module` that re-exports all generated types (`CMD*`, `Entry*`, chain struct, dispatcher static) for pathf / external access.
+
+ Internally, the implementation:
+ - Parses `#[command(...)]` arguments via `CommandArgs` supporting `node`, `name`, `entry` keys and extension paths.
+ - Validates function constraints (no `self`, at least one parameter).
+ - Handles async functions (rejected without the `async` feature).
+ - Resolves default names via `just_fmt::dot_case!` / `just_fmt::pascal_case!`.
+ - Builds a chain wrapper that calls the original function with `entry.into()` for the first argument.
+
+4. **[`pathf:patterns`]** Added `CommandPattern` to the `pathf` pattern analyzer, matching functions annotated with `#[command]`. The pattern tracks the generated hidden module (`__command_{fn}_module`) and marks it as a local module item via `AnalyzeItem::local_module()`. The build system generates a glob re-export `use path::__command_{fn}_module::*;` to bring all generated types (`Entry*`, `CMD*`, chain struct, dispatcher static) into scope.
+
+5. **[`macros:dispatcher`]** Added a `From<pack_Type> for crate::Entry` implementation inside the `dispatcher!()` macro expansion. When the `dispatcher!()` macro generates the entry pack type (via `pack!(#pack = Vec<String>)`), it now also generates `impl From<#pack> for crate::Entry { fn from(value: #pack) -> Self { crate::Entry::new(value.inner) } }`. This allows pack types generated by `dispatcher!()` to be directly converted into `crate::Entry`, enabling ergonomic integration with program-level entry handling.
+
+6. **[`macros:gen_program`]** Added a `pack!(Entry = Vec<String>)` invocation inside the `__this_program_impl` module generated by `gen_program!()`. This creates a `Entry` pack type (aliasing a `Vec<String>` container) directly in the generated module, providing a default entry point type for the program that can be used by `dispatcher!()`-generated types and other chain infrastructure without requiring the user to define a separate entry pack type manually.
+
+7. **[`core`]** **[`comp`]** Added `Suggest::combine(self, other: impl Into<Suggest>) -> Self` method that merges two `Suggest` values. If both are `Suggest::Suggest`, their inner `BTreeSet`s are merged (all items from `other` are added into `self`). Otherwise, the first `Suggest::Suggest` (or `FileCompletion`) is returned unchanged, and the other value is discarded. This enables ergonomic aggregation of completion suggestions from multiple sources.
+
+**[`features`]** Added preset feature groups to `mingling/Cargo.toml`, providing convenience combinations for common use cases:
+
+- **`mini`** — `extras`, `picker`. Minimal mode for small CLI tools.
+- **`advanced`** — `extras`, `picker`, `repl`, `comp`, `dispatch_tree`, `structural_renderer`. Full-featured mode for medium-sized applications.
+- **`full`** — `extras`, `picker`, `repl`, `clap`, `comp`, `dispatch_tree`, `structural_renderer_full`, `pathf`. Complete mode for large, feature-comprehensive applications.
+- **`build_advanced`** — `build`, `comp`. Build-time configuration for generating completion scripts etc.
+- **`build_full`** — `build`, `comp`, `pathf`, `dispatch_tree`. Full build-time configuration including the path analyzer.
+
+ `build_advanced` and `build_full` are intended for use in `[build-dependencies]` alongside their corresponding runtime feature groups.
+
+ Also reorganized the `[features]` section of `mingling/Cargo.toml` into logical subsections (Presets, Core, Special features, Features, LEGACY) for improved maintainability and documentation.
+
+8. **[`core`]** **[`macros`]** Added the `#[completion(EntryFallback)]` syntax to the `#[completion]` macro, mirroring the `#[help(EntryFallback)]` pattern. When the `EntryFallback` identifier is passed (either as a bare path in the attribute arguments), the macro generates a chain handler for the `EntryFallback` type (the program's fallback entry pack type generated by `gen_program!()`). This handler is invoked during the default-completion path when no explicit dispatcher match is found.
+
+ When the program's dispatcher fails to find a match in the completion pipeline, the `CompletionHelper::complete` method now:
+
+ - Checks for an explicit completion handler via the chain pipeline for the `EntryFallback` type (calling `P::do_comp(&P::build_entry_fallback(vec![]), ctx)`).
+ - If that produces suggestions, they are combined with the default completion suggestions via `Suggest::combine()` (which merges `BTreeSet`s for `Suggest::Suggest` values).
+ - Falls back to the standard `default_completion::<P>(ctx)` behavior when no explicit fallback handler yields results.
+
+ This enables users to provide custom completion suggestions for the fallback/unmatched case:
+
+ ```rust,ignore
+ #[completion(EntryFallback)]
+ fn complete_fallback(_ctx: &ShellContext) -> Suggest {
+ suggest! { "fallback" }
+ }
+ ```
+
+ The generated chain handler for `EntryFallback` is identical to how `#[chain]`-annotated functions targeting entry pack types work — the `EntryFallback` type name is resolved through the same `chain` code-generation path used for `Entry{Type}` types, allowing users to write a dedicated completion function for the fallback entry point without manually registering it in the program's dispatcher.
+
+ Internal changes:
+ - `mingling_macros/src/attr/completion.rs` updated to detect the `EntryFallback` identifier in attribute arguments.
+ - `CompletionHelper::complete` in `mingling_core/src/comp.rs` now invokes the fallback completion handler via `P::do_comp(&P::build_entry_fallback(vec![]), ctx)` and merges results with `Suggest::combine()`.
+
+9. **[`core`]** **[`macros`]** Added a compile-time **entry metadata** system that allows attaching arbitrary, compile-time-typed metadata to entries and retrieving it at runtime.
+
+ - **`Metadata<B>` trait** — Added to `mingling_core::asset::metadata` and re-exported from `mingling_core` / `mingling`. A type implementing `Metadata<B>` for an entry variant `E` provides `init_metadata() -> B`, defining the metadata value for that entry.
+
+ - **`#[metadata(EntryVariant)]` attribute macro** — Added `mingling_macros::metadata`, which converts a zero-argument function into:
+ - an `impl ::mingling::Metadata<ReturnType> for EntryVariant` whose `init_metadata()` calls the original function,
+ - a `register_metadata!(EntryVariant, ReturnType)` invocation that populates the global `METADATA` registry,
+ - the preserved original function unchanged (including attributes, visibility, and return signature).
+
+ Requirements: the function must take no parameters, must have an explicit return type, and cannot be async.
+
+ - **`register_metadata!(EntryVariant, MetadataType)` macro** — Added `mingling_macros::register_metadata` (doc-hidden) which parses the two type arguments and stores a match-arm-style string entry in the `METADATA` global registry for later consumption by `gen_program!`.
+
+ - **`ProgramCollect::get_metadata<T>(member_id) -> Option<T>`** — Added a default method on `ProgramCollect` that returns `None`. The `gen_program!` macro now overrides it: if the `METADATA` registry is non-empty, it generates a `get_metadata` implementation that matches on the enum member, compares the requested `TypeId::of::<T>()` against each registered metadata type's `TypeId`, and downcasts the boxed `Any` to `T`.
+
+ - **`pathf` integration** — Added `MetadataPattern` to `mingling_pathf` that matches functions annotated `#[metadata(BindType)]`, extracting both the `BindType` (attribute argument, always a local in-crate entry type) and the `DataType` (the function's return type — resolved as local or foreign via `use` imports) so `pathf` emits the appropriate `use` statements for `gen_program!`.
+
+ Usage:
+
+ ```rust,ignore
+ #[metadata(EntryGreet)]
+ pub fn greet_desc() -> Description {
+ Description { desc: "ok".to_string() }
+ }
+
+ // Later, at runtime:
+ let desc = ThisProgram::get_metadata::<Description>(ThisProgram::EntryGreet);
+ ```
+
+ The `#[metadata]` attribute and `Metadata` trait are re-exported as `mingling::macros::metadata` and `mingling::Metadata` respectively.
+
+10. **[`metadata:description`]** Added the `mingling::metadata` module and the `Description` convention metadata type. The `Description` type provides a human-readable description for any `Grouped` type, designed to be attached via the `#[metadata]` attribute macro introduced in item 9 above.
+
+ The `Description` struct wraps a `String` and provides:
+
+ - **`Description::new<S: Into<String>>(desc: S) -> Description`** — Constructs a new `Description` from any value convertible to `String`.
+ - **`From<String>`** / **`From<&str>`** — Constructs a `Description` from an owned `String` or a string slice.
+ - **`From<Description> for String`** / **`From<&Description> for String`** — Extracts the inner `String` (or a clone) from a `Description` value.
+ - **`Deref<Target = str>`** / **`DerefMut`** — Allows `Description` to be used transparently as a `str`, so string methods (`len()`, `contains()`, etc.) work directly on it.
+ - **`Display`** — Formats the description as its inner string, so `Description` can be used directly with `format!`, `print!`, and `String::from`-style operations.
+
+ Usage:
+
+ ```rust,ignore
+ use mingling::metadata::Description;
+
+ #[metadata(EntryGreet)]
+ pub fn greet_desc() -> Description {
+ Description::new("Greets the user by name.")
+ }
+
+ // Later, at runtime:
+ let desc = ThisProgram::get_metadata::<Description>(ThisProgram::EntryGreet);
+ println!("{desc}"); // "Greets the user by name."
+ ```
+
+ The module is gated behind the `core` feature and re-exported as `mingling::metadata`. This type is designed to work hand-in-hand with the compile-time entry metadata system from item 9, providing a first-party convention metadata for describing entries in generated documentation and help output.
+
+11. **[`core:comp`]** Enhanced `default_completion` to attach each suggestion's owning entry's `Description` metadata as a completion description. A new helper `entry_description::<P>(node)` resolves a space-separated command node path to its owning entry's member id (via the dispatch trie under `dispatch_tree`, or `match_user_input` + dispatcher `begin` otherwise), then retrieves the `Description` via `ProgramCollect::get_metadata::<Description>`. If found, suggestions for that node are built as `SuggestItem::new_with_desc(token, desc)` instead of plain `SuggestItem::new(token)`.
+
+ The suggestion collection was also reworked: it now uses a `BTreeSet<SuggestItem>` for natural ordering and deduplication (replacing the previous `Vec<String>` + manual `sort()`/`dedup()`), carries both the suggested token and the fully-qualified owner node path used for the description lookup, and returns `Suggest::Suggest(suggestions)` directly. `entry_description` returns `None` for intermediate trie segments that have no entry of their own or entries without a registered `Description`, in which case suggestions fall back to plain `SuggestItem::new(token)`. The final empty-suggestions fallback to `file_suggest()` is unchanged.
+
+12. **[`core:render`]** Reworked the `RenderResult` immediate-output mechanism from a single boolean flag into a general **print-hook** system, enabling multiple user-defined hooks to be invoked with the content and output mode of every write.
+
+ **`RenderResultPrint` struct** — Added a new public struct bundling the emitted text content and its output mode:
+
+ - **`content: String`** — The raw text written. For `println`/`eprintln` it includes the trailing newline; for `print`/`eprint` it is exactly the given text.
+ - **`mode: RenderResultMode`** — The output mode (`Stdout` or `Stderr`) the content was written with, telling the hook where the content belongs.
+
+ Derives `Debug`, `Clone`, `PartialEq`, `Eq`.
+
+ **`print_hook` field** — Replaced `immediate_output: bool` with `print_hook: PrintHook` (a `Vec<Box<dyn FnMut(RenderResultPrint)>>` inside an `Option`). The default is `None`, meaning content is only buffered and output uniformly at the end (e.g. via `std_print`).
+
+ **`bind_print_hook()` method** — New method that pushes a user-provided hook onto the hook list; multiple hooks can be bound and are invoked in binding order. Returns `&mut Self` for chaining.
+
+ **`immediate_output()` behavior change** — Now calls `bind_print_hook()` with a hook that flushes content to stdout/stderr in real time (functionally identical to the old boolean behavior, but implemented via the hook mechanism). No longer `const`.
+
+ **`emit()` private helper** — Iterates bound hooks and invokes each with a `RenderResultPrint { content, mode }` value.
+
+ **Write methods updated** — `print`, `println`, `eprint`, and `eprintln` now call `self.emit(&text, Stdout/Stderr)` (after formatting the trailing newline for `println`/`eprintln`) instead of checking the `immediate_output` flag.
+
+ **`append_other()` semantics** — Now checks whether _self has hooks_ and _other has none_; when true, other's buffered content is emitted through self's hooks while being appended. The other's hooks and `exit_code` are **not** transferred — only its buffered content is merged.
+
+ **Manual trait impls** — Since hooks are opaque closures that cannot be cloned or meaningfully compared, hand-written impls replaced the derives:
+
+ - **`Clone`** — Clones the buffered content and exit code but drops the print hooks (creates a result with no hooks).
+ - **`PartialEq` / `Eq`** — Compares only the render buffer and exit code; hooks are ignored.
+ - **`Debug`** — Prints the buffered content, exit code, and the _number_ of bound hooks (`print_hooks: Vec::len`), avoiding attempting to format opaque closures.
+
+ `Default` is still derived (all fields default to empty/`None`).
+
+13. **[`core:utils`]** Added a new `utils` module to `mingling_core` with the `ArgumentSplitter` trait, providing a reusable implementation of shell-style command-line argument splitting. The trait is implemented for `str` and internally for `String`, and provides a `split_args()` method that splits a string input into a `Vec<String>` of arguments, respecting single quotes, double quotes, and backslash escaping.
+
+ **Trait definition:**
+
+ ```rust
+ pub trait ArgumentSplitter {
+ /// Splits the string into a `Vec<String>` of arguments, respecting
+ /// single quotes, double quotes, and backslash escaping.
+ fn split_args(&self) -> Vec<String>;
+ }
+
+ impl ArgumentSplitter for str { /* ... */ }
+ impl ArgumentSplitter for String { /* ... */ }
+ ```
+
+ **Splitting rules** (identical semantics to the previously private `splitter` module):
+
+ - **Whitespace separation** — Arguments are separated by spaces (`' '`); consecutive spaces collapse and empty tokens are dropped.
+ - **Single/double quotes** — Text inside `'...'` or `"..."` is treated as a single argument; the quote characters are stripped. Within quoted segments, backslash escapes the next character (so `"b c"` produces `b c`, and `"b\"c"` produces `b"c`).
+ - **Backslash escaping** (outside quotes) — A backslash takes the next character literally (e.g., `b\ c` produces `b c`; `b\"c` produces `b"c`); a trailing backslash with no following character is ignored/lost.
+
+ **Integration:** The previously private `split_input` / `split_input_string` functions in `mingling_core::program::repl_exec::splitter` have been removed; `repl_exec` now uses `readline.split_args()` on the input string. The `utils` module is exported as `mingling_core::utils`.
+
+ The `ArgumentSplitter` trait is a public API addition, so downstream code can now reuse the same argument-splitting logic that the REPL uses for parsing input lines.
+
+14. **[`confirm`]** Added the `Confirmer` resource and its confirmation predicates:
+
+ - **`mingling::res::Confirmer`** — A new resource type for interactive confirmation prompts. It caches the confirmed state to avoid repeated prompts, and is typically registered via [`ConfirmerSetup`] and injected into functions through Mingling's resource injection system.
+
+ - **`Confirmer::new()`** — Creates a new `Confirmer` instance in the unconfirmed state.
+ - **`Confirmer::new_confirmed()`** — Creates a `Confirmer` in the confirmed state; `ask`/`try_ask` return `true` directly without prompting.
+ - **`set_confirmed(&mut self)`** — Marks the confirmer as confirmed; subsequent `ask`/`try_ask` calls return `true` directly.
+ - **`ask<P: ConfirmerPredicate>(&self, ask: impl AsRef<str>) -> bool`** — Prompts the user at most **one** time. Returns `false` if the user provides an unrecognizable answer, `true` if already confirmed.
+ - **`try_ask<P: ConfirmerPredicate>(&self, ask: impl AsRef<str>, count: impl Into<ConfirmerCount>) -> Option<bool>`** — Prompts the user up to `count` times. Returns `Some(true)` for confirmation, `Some(false)` for rejection, and `None` if the maximum attempts are exhausted without a parseable answer. The prompt is written to stderr.
+
+ - **`mingling::confirm::ConfirmerCount`** — Specifies the maximum number of attempts: `Loop` (0, indefinite) or `Max(usize)` (positive integer). `From` impls are provided for all integer types (`i8`, `i16`, `i32`, `i64`, `i128`, `isize`, `u8`, `u16`, `u32`, `u64`, `u128`, `usize`); `0` maps to `Loop`, negative values clamp to `Max(usize::MAX)`.
+
+ - **`mingling::confirm::ConfirmerPredicate`** — Defines how to parse user confirmation input. Implementors provide `is_yes(str: &str) -> Option<bool>`: `Some(true)` for yes, `Some(false)` for no, `None` for unparseable input (requiring re-entry).
+
+ - **`mingling::confirm::YesConfirm`** — Accepts `"y"`/`"yes"` as yes and `"n"`/`"no"` as no. Case-insensitive with leading/trailing whitespace trimming.
+
+ - **`mingling::confirm::TrueConfirm`** — Accepts `"true"`/`"t"` as yes and `"false"`/`"f"` as no. Case-insensitive with leading/trailing whitespace trimming.
+
+ The `Confirmer` resource derives `Debug`, `Default`, `Clone`, `Copy`. The `confirm` module also houses `ConfirmerCount`, `ConfirmerPredicate`, `YesConfirm`, and `TrueConfirm` at `mingling::confirm::*`.
+
+15. **[`setups`]** Added the `ConfirmSetup` and `StandardInputArgsSetup` program setups:
+
+ ### `ConfirmSetup`
+ - **`mingling::setup::ConfirmSetup`** — A `ProgramSetup` that registers a `ResConfirm` resource and installs a pre-dispatch hook checking the user's `confirmation` config mode. When `program.user_context.confirmation == ConfirmationMode::Skip`, the hook marks the `ResConfirm` as confirmed via `modify_res`, so all `ask`/`try_ask` calls return `true` without prompting.
+
+ - Registered via `program.with_setup(ConfirmSetup)`.
+ - Applies uniformly to all subcommands of the entire program; it does not support per-command overrides.
+
+ ### `StandardInputArgsSetup`
+ - **`mingling::setup::StandardInputArgsSetup`** — A `ProgramSetup` that reads piped/redirected standard input and appends the split arguments to the end of the program's argument list.
+
+ - A `pre_dispatch` hook checks whether stdin is a terminal via `IsTerminal`; if it is **not** a terminal (i.e., there is piped or redirected input), it reads all of stdin to the end, converts it to UTF-8 (strict first, lossy fallback), splits it via `ArgumentSplitter::split_args()` (whitespace, single/double quotes, backslash escaping), and appends the resulting arguments to `ctx.arguments` (a `&mut Vec<String>`, per the `pre_dispatch` hook's mutable-arguments semantics from **BREAKING CHANGE #6** in this release).
+ - Empty input produces no arguments.
+ - **Note:** the setup does **not** validate input — stdin content is treated as trusted arguments appended directly, so untrusted input can inject arbitrary arguments. It also has no per-subcommand granularity; if different subcommands need different stdin behavior, do not use this setup.
+
+16. **[`osc94`]** **[`setups:osc94`]** Added the `ResOSC94` resource and `OSC94Setup` for managing terminal `OSC 9;4` protocol status:
+
+ ### `ResOSC94` resource
+ - **`mingling::res::ResOSC94`** — A new resource type providing support for the [OSC 9;4 protocol](https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences), which allows sending task progress notifications via ANSI escape sequences. It is typically registered via [`OSC94Setup`] and injected into functions through Mingling's resource injection system.
+
+ - **`ResOSC94::get_mut(&self) -> OSC94Guard`** — Returns an [`OSC94Guard`] with an initial state of [`OSC94State::Clean`]. If the current environment supports the `OSC 9;4` protocol, state changes will be sent to the terminal in real time.
+
+ Derives `Debug`, `Default`, `Clone`, `Copy`.
+
+ ### `OSC94Guard`
+ - **`mingling::osc94::OSC94Guard`** — A guard for modifying process state, obtained via [`ResOSC94::get_mut`]. When the guard is dropped, the process state is automatically restored to [`OSC94State::Clean`], so no manual cleanup is needed.
+
+ - **`set_clean_state(&mut self)`** — Sets the process state to Clean, indicating the process has finished or is in a normal, problem-free state.
+ - **`set_error_state(&mut self)`** — Sets the process state to Error, indicating an error occurred during process execution.
+ - **`set_warn_state(&mut self)`** — Sets the process state to Warn, indicating a warning occurred but hasn't reached error level.
+ - **`set_unknown_state(&mut self)`** — Sets the process state to Unknown, indicating the process state cannot be determined or has not been defined.
+ - **`set_progress(&mut self, progress: f32)`** — Sets the progress value (should be between `0.0` and `1.0`; values outside this range are not clamped, but it is recommended to keep them within range).
+ - **`state(&self) -> OSC94State`** — Returns the current process state.
+ - **`progress(&self) -> f32`** — Returns the actual progress value only when the state is `OSC94State::Normal`; otherwise returns `0.0`.
+
+ ### `OSC94State` enum
+ - **`mingling::osc94::OSC94State`** — Represents the `OSC 9;4` protocol message state:
+
+ - **`Clean`** — Clears/hides progress (used when task completes), corresponding to state code `0`.
+ - **`Normal(f32)`** — Normal state, state code `1`, requires a progress value (0-100).
+ - **`Error`** — Error state, state code `2` (usually displayed in red).
+ - **`Unknown`** — Uncertain state, state code `3` (shown as an indeterminate animation for unknown progress).
+ - **`Warn`** — Warning state, state code `4` (usually displayed in yellow).
+
+ - **`state_code(&self) -> u8`** — Returns the state code for the `OSC 9;4` protocol.
+ - **`progress(&self) -> f32`** — Returns the progress value (0-100) for the `Normal` state, clamped to the valid range.
+ - **`to_escape_sequence(&self) -> String`** — Converts the message into the corresponding `OSC 9;4` escape sequence string.
+ - **`send(&self)`** — Sends the `OSC 9;4` message to the terminal via stdout. Panics if the stdout stream cannot be flushed.
+
+ Implements `Display` (formats as the escape sequence), `From<OSC94State> for String`, and `From<&OSC94State> for String`. Derives `Debug`, `Clone`, `Copy`, `PartialEq`.
+
+ ### `OSC94Setup`
+ - **`mingling::setup::OSC94Setup`** — A `ProgramSetup` that registers an `ResOSC94` resource in the program's resource store, with its `is_support` flag determined at setup time by inspecting environment variables. The support check looks at:
+
+ - **`TERM_PROGRAM`** — `ghostty`, `WezTerm`, `iTerm.app`
+ - **`WT_SESSION`** — Windows Terminal
+ - **`VTE_VERSION`** — VTE-based terminals (such as GNOME Terminal, Konsole, etc.)
+ - **`TERM`** — terminal emulators containing `xterm`
+
+ Registered via `program.with_setup(OSC94Setup)`.
+
+ Usage example:
+
+ ```rust,ignore
+ use mingling::{macros::command, res::ResOSC94, setup::OSC94Setup};
+
+ fn main() {
+ let mut program = ThisProgram::new();
+ program.with_setup(OSC94Setup);
+ program.exec_and_exit();
+ }
+
+ #[command]
+ fn hello(osc: &ResOSC94) {
+ let mut guard = osc.get_mut();
+ guard.set_progress(0.5);
+ // ... do work ...
+ guard.set_warn_state();
+ // ... guard is dropped, state automatically restored to Clean
+ }
+ ```
+
+#### **BREAKING CHANGES** (API CHANGES):
+
+1. **[`macros`]** **[BREAKING]** Renamed the `extra_macros` feature to `extras`. All feature-gated macro re-exports in `mingling/src/lib.rs` (and throughout the codebase) have been updated from `#[cfg(feature = "extra_macros")]` to `#[cfg(feature = "extras")]`.
+
+ **Affected macros** (all previously gated behind `extra_macros`, now `extras`):
+ - `#[command]`
+ - `empty_result!`
+ - `entry!`
+ - `group!`
+ - `group_structural!` (also requires `structural_renderer`)
+ - `pack_err!`
+ - `pack_err_structural!` (also requires `structural_renderer`)
+ - `#[program_setup]`
+ - `render_route!`
+ - `#[renderify]`
+ - `route!`
+ - `#[routeify]`
+
+ **Migration guide:**
+ - Update `Cargo.toml` feature declarations from `extra_macros` to `extras`.
+ - If your code references `mingling::feature::MINGLING_EXTRA_MACROS`, update it to `mingling::feature::MINGLING_EXTRAS`.
+
+ _No behavioral changes — this is a pure feature rename. The `extras` feature provides identical functionality to the old `extra_macros` feature; all prelude and macros module re-exports remain the same under the new feature name._
+
+2. **[`core`]** **[BREAKING RENAME]** Renamed the internal fallback type `ErrorDispatcherNotFound` and its associated `ProgramCollect` plumbing to `EntryFallback` / `build_entry_fallback`.
+
+ **Associated type renames (on `ProgramCollect`):**
+
+ - `type ErrorDispatcherNotFound` → `type EntryFallback`
+ - `fn build_dispatcher_not_found(args)` → `fn build_entry_fallback(args)`
+
+ **Type/enum renames generated by `gen_program!()`:**
+
+ - Enum variant `ThisProgram::ErrorDispatcherNotFound` → `ThisProgram::EntryFallback`
+ - Pack type `ErrorDispatcherNotFound` → `EntryFallback` (created by `program_fallback_gen!()` via `pack!(EntryFallback = Vec<String>)`)
+
+ **Migration guide (for downstream code):**
+
+ - Renderer/help functions that previously took `ErrorDispatcherNotFound` as a parameter must now take `EntryFallback`.
+ - Any code referencing `C::build_dispatcher_not_found(...)` must now call `C::build_entry_fallback(...)`.
+ - Any manual `ProgramCollect` implementation (e.g., in tests or mocks) must rename both the associated type `ErrorDispatcherNotFound` → `EntryFallback` and the associated method `build_dispatcher_not_found` → `build_entry_fallback`.
+
+ _No behavioral changes — this is a pure rename of the internal fallback type and its associated `ProgramCollect` methods. The type's semantics, shape (wrapping `Vec<String>`), and rendering behavior are unchanged.
+
+3. **[`picker:global`]** **[`setups:picker`]** Refactored the global picker utility functions into a trait-based API. The standalone functions `pick_global_flag(program, flag)` and `pick_global_argument(program, arg)` in `mingling::picker` have been replaced by the `PickerHelper<C>` trait, implemented for `Program<C>`. This trait provides `pick_flag(&mut self, flag: &PickerArg<Flag>) -> bool` and `pick_argument<A>(&mut self, arg: &PickerArg<A>) -> Option<A>`, and is backed by the `take_args` / `replace_args` methods on `Program`.
+
+ **Migration guide:**
+
+ - `pick_global_flag(program, flag)` → `program.pick_flag(flag)`
+ - `pick_global_argument(program, arg)` → `program.pick_argument(arg)`
+ - Import `mingling::picker::PickerHelper` instead of `mingling::picker::{pick_global_flag, pick_global_argument}`
+
+4. **[`core:program`]** **[BREAKING]** Refactored the program configuration settings into typed enums and moved them to a dedicated `config` module. The previously boolean-based fields in `ProgramStdoutSetting` and `ProgramUserContext` have been replaced with new semantic enum types, improving type safety and self-documentation. Additionally, all configuration types — including `StructuralRendererSetting` — have been moved under `mingling_core::config`, with the `config` module made public.
+
+ **`ProgramStdoutSetting` changes:**
+
+ - `error_output: bool` → `error_output: ErrorOutput` — enum with `Show`/`Hide` variants
+ - `render_output: bool` → `render_output: RenderOutput` — enum with `Show`/`Hide` variants
+ - `silence_panic: bool` → `silence_panic: PanicSilence` — enum with `Show`/`Silence` variants
+ - `verbose: bool`, `quiet: bool`, `debug: bool` → `verbosity: Verbosity` — single enum with `Normal`/`Verbose`/`Quiet`/`Debug` variants
+ - `color: bool` → `color: ColorOutput` — enum with `Enabled`/`Disabled` variants
+ - `progress: bool` → `progress: ProgressOutput` — enum with `Enabled`/`Disabled` variants
+
+ **`ProgramUserContext` changes:**
+
+ - `confirm: bool` → `confirmation: ConfirmationMode` — enum with `Confirm`/`Skip` variants
+ - `dry_run: bool`, `force: bool` → `execution: ExecutionMode` — single enum with `Normal`/`DryRun`/`Force` variants
+ - `interactive: bool` → `interaction: InteractionMode` — enum with `Interactive`/`NonInteractive` variants
+ - `assume_yes: bool` → `yes_assumption: YesAssumption` — enum with `None`/`AssumeYes` variants
+
+ **Default values:**
+
+ - `ProgramStdoutSetting::default()`: `ErrorOutput::Show`, `RenderOutput::Show`, `PanicSilence::Show`, `Verbosity::Normal`, `ColorOutput::Enabled`, `ProgressOutput::Enabled`
+ - `ProgramUserContext::default()`: `confirmation: ConfirmationMode::Confirm`, `execution: ExecutionMode::Normal`, `interaction: InteractionMode::NonInteractive`, `yes_assumption: YesAssumption::None`
+
+ **Module reorganization:**
+
+ - All config types are defined in `mingling_core::program::config`, which is now a public module (`pub mod config`).
+ - `StructuralRendererSetting` (previously exposed at the crate root) now lives in `mingling_core::config` and must be referenced as `mingling_core::config::StructuralRendererSetting`.
+ - Internal usages across `program.rs`, `collection.rs`, `mock.rs`, `once_exec.rs`, `hook.rs`, `structural.rs`, and setup modules have been updated to reference the new `config::` path.
+ - The `config` module is re-exported from the `mingling_core` crate root as `mingling_core::config`.
+
+ **Migration guide:**
+
+ - `s.error_output = true` → `s.error_output = ErrorOutput::Show` (and `false` → `ErrorOutput::Hide`)
+ - `s.render_output = true` → `s.render_output = RenderOutput::Show`
+ - `s.silence_panic = true` → `s.silence_panic = PanicSilence::Silence`
+ - `s.verbose = true` → `s.verbosity = Verbosity::Verbose`
+ - `s.quiet = true` → `s.verbosity = Verbosity::Quiet`
+ - `s.debug = true` → `s.verbosity = Verbosity::Debug`
+ - `s.color = true` → `s.color = ColorOutput::Enabled`
+ - `s.progress = true` → `s.progress = ProgressOutput::Enabled`
+ - `ctx.confirm = true` → `ctx.confirmation = ConfirmationMode::Skip`
+ - `ctx.dry_run = true` → `ctx.execution = ExecutionMode::DryRun`
+ - `ctx.force = true` → `ctx.execution = ExecutionMode::Force`
+ - `ctx.interactive = true` → `ctx.interaction = InteractionMode::Interactive`
+ - `ctx.assume_yes = true` → `ctx.yes_assumption = YesAssumption::AssumeYes`
+ - References to `mingling_core::StructuralRendererSetting` → `mingling_core::config::StructuralRendererSetting`
+
+ _Behavioral semantics are preserved — the same configuration states are expressible in both the old boolean form and the new enum form, but the typed enums eliminate impossible states (e.g., both `verbose` and `quiet` set simultaneously) and make the intent of each setting explicit through named variants._
+
+5. **[`modify_picker`]** Removed the invalid naming style variants from `ParserStyleNamingCase`: `Title`, `Lower`, and `Upper`. These variants originally corresponded to the `just_fmt` macros `title_case!`, `lower_case!`, and `upper_case!`, but in practice these styles are not valid naming conventions. They have therefore been removed from the enum definition and its `to_format_string` / related formatting matches.
+
+ **Changes involved:**
+
+ - **`ParserStyleNamingCase` enum** — Removed the `Title`, `Lower`, and `Upper` variants along with their doc comments.
+ - **Formatting implementation** — Removed the `Self::Title => just_fmt::title_case!(...)`, `Self::Lower => just_fmt::lower_case!(...)`, and `Self::Upper => just_fmt::upper_case!(...)` branches from the enum's `fmt` match.
+
+ _This is a pure deletion change with no behavioral replacement. If downstream code used the `Title`, `Lower`, or `Upper` variants, it needs to switch to other naming styles (such as `Pascal`, `Kebab`, `Snake`, or `Dot`)._
+
+6. **[`core:hook`]** **[BREAKING]** Changed `HookPreDispatchInfo.arguments` from `&'a [String]` (immutable slice) to `&'a mut Vec<String>` (mutable reference), and updated the `pre_dispatch` hook signature accordingly. The `pre_dispatch` hook can now **rewrite the program's command-line arguments before they are matched against registered dispatchers**, enabling argument normalization, injection, or filtering at the hook level.
+
+ **Type changes:**
+
+ - `HookPreDispatchInfo.arguments: &'a [String]` → `arguments: &'a mut Vec<String>`
+ - `ProgramHook<C>::pre_dispatch` field type: `Box<dyn for<'a> Fn(&HookPreDispatchInfo<'a>) -> ProgramControls<C>>` → `Box<dyn for<'a> Fn(&mut HookPreDispatchInfo<'a>) -> ProgramControls<C>>`
+ - `ProgramHookBuilder::on_pre_dispatch<F, R>` bound: `F: for<'a> Fn(&HookPreDispatchInfo<'a>) -> R` → `F: for<'a> Fn(&mut HookPreDispatchInfo<'a>) -> R`
+ - `Program::run_hook_pre_dispatch` parameter: `&HookPreDispatchInfo` → `&mut HookPreDispatchInfo`
+
+ **Execution pipeline changes:**
+
+ - `exec()` now clones `program.args` into a local mutable `Vec<String>` and passes `&mut` to `exec_with_args`, so hooks rewriting arguments do not mutate the program's stored args (the rewritten copy is what gets dispatched).
+ - `exec_with_args` now takes `args: &mut Vec<String>` instead of `args: &[String]`, and passes `&mut *args` into `run_hook_pre_dispatch`.
+ - `ProgramOnceExec::once_exec` takes the program args via `std::mem::take` into a local mutable `Vec<String>` and passes `&mut` through to `exec_with_args`, ensuring the same mutable-args semantics in the `once_exec` path.
+ - `ReplExec::exec` and `exec_once` in `repl_exec.rs` now pass `&mut Vec<String>` through the same execution path.
+
+ **Migration guide:**
+
+ - Any `pre_dispatch` hook closures must now accept `&mut HookPreDispatchInfo` instead of `&HookPreDispatchInfo`:
+
+ ```rust
+ // Before
+ .on_pre_dispatch(|info: &HookPreDispatchInfo| { ... })
+
+ // After
+ .on_pre_dispatch(|info: &mut HookPreDispatchInfo| {
+ // info.arguments is now &mut Vec<String> — can be mutated
+ })
+ ```
+
+ - References to `info.arguments` inside hooks that previously treated it as `&[String]` will need to adapt to `&mut Vec<String>` (e.g., `info.arguments.as_slice()` instead of `info.arguments` in comparison positions, or explicit dereference / indexing changes).
+ - While most `&[String]`-style usages are transparently compatible via `Deref`, code that relied on the immutability guarantee of the slice (e.g., passing `info.arguments` to functions expecting `&[String]` via coercion) may need `info.arguments.as_slice()` or `&*info.arguments`.
+
+ _Behavioral change: hooks can now rewrite the argument list before dispatch — e.g., inserting default flags, removing deprecated options, or expanding shorthand syntax. Hook authors should be careful to preserve the program's expected argument layout when mutating the list._
+
+---
+
+### Release 0.3.0 (2026-07-27)
+
+> In detail, the changes in Mingling 0.3.0 are as follows:
+
+1. **Added `arg-picker`** — Mingling has never had a comfortable argument parsing solution. You either suffered with `parser` or went all-in on `clap`. So I wrote a smarter `arg-picker`. The API style is close to the original `parser`, but it's more type-safe, more robust, and more extensible. See the main text for details.
+
+2. **Made implicit behavior explicit**. For a long time, Mingling's attribute macros have been making **implicit** modifications to the original function — I have to admit, that's dirty. In the new version, I've removed **all** implicit modifications to the original function by attribute macros. In other words, `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` will no longer modify your original function in any way unless you explicitly specify it. Use the Extension Attribute (`#[chain(/* ... */)]`) mechanism to explicitly inject implicit behavior into your functions.
+
+3. I was originally planning to remove `r_println!` because I couldn't stand that `__renderer_inner_result` thing implicitly injected by the `#[renderer]` macro. But now I've rewritten it: `#[buffer]` injects an implicit `__render_result_buffer` value into the function, and then `r_println!` calls it. It's an extra step, but it also means: the dirt is your choice, not something I'm forcing on you :)
+
+4. **Finally**, a philosophical point. Mingling will move forward with a preference for **"selectively dirty"** over **"invisibly, forcibly dirty"** — this is the biggest direction going forward, building a more comfortable API on this foundation.
#### Fixes:
@@ -63,6 +1096,14 @@ None
2. **[`pathf:patterns`]** Updated `PackPattern` detection to recognize `pack_structural!` and `pack_err_structural!` macros, which were previously missed by the pattern matcher. The `contains` method now checks for these additional macro names alongside the existing `pack!` and `pack_err!` checks.
+3. **[`pathf:patterns`]** Added `is_foreign` field to `AnalyzeItem` struct in `mingling_pathf`, along with constructor helpers `AnalyzeItem::local()` and `AnalyzeItem::foreign()`. The `foreign()` constructor marks items resolved via `use` imports, so their `module` path is used as-is (rather than being prefixed with the file's module path) in `type_mapping_builder.rs`.
+
+ - **`GroupPattern`** updated to collect `use` imports at the file and inline-module level via `collect_use_imports()` and `collect_from_use_tree()`, and to resolve `group!(TypeName)` invocations against those imports: if a name matches an imported type, it is emitted as `AnalyzeItem::foreign()` with the full import path; if the `Alias = path::Type` form is used, it is emitted as `AnalyzeItem::local()` (the alias lives in-crate); otherwise it is `local()`.
+
+ - All other patterns (`BasicStructPattern`, `ChainPattern`, `CompletionPattern`, `DispatcherPattern`, `DispatcherClapPattern`, `GroupedDerivePattern`, `HelpPattern`, `PackPattern`, `RendererPattern`) updated to use `AnalyzeItem::local()` constructors, preserving existing behavior (all items are treated as local/in-crate).
+
+ - **`type_mapping_builder.rs`** updated to check `ai.is_foreign`: when true, the full path is built as `{module}::{item_name}` (no prefix from the file's own module path); when false, the existing logic applies (`{file_module_path}::{module}::{item_name}` or `{file_module_path}::{item_name}`).
+
#### Optimizations:
1. **[`macros`]** Updated `route!` macro to use `Routable` trait instead of `Grouped` trait for error conversion, making the semantics clearer. The `route!` macro now calls `::mingling::Routable::to_chain(e)` on the error branch instead of `::mingling::Grouped::to_chain(e)`.
@@ -73,11 +1114,11 @@ None
The `Routable` trait is defined in `mingling_core::asset::routable` and provides unified routing capabilities (`to_chain` / `to_render`) for any type that can be dispatched into the program's pipeline. A blanket implementation is provided for all `T: Grouped<C> + Send`, ensuring backward compatibility — existing types that implement `Grouped` automatically implement `Routable`.
-2. **[`macros`]** Restructured the `mingling_macros` crate's internal module hierarchy. The previously flat module structure has been reorganized into a logical directory-based layout:
+2. **[`macros`]** Restructured the `mingling_macros` crate's internal module hierarchy. The previously flat module structure has been reorganized into a logical directory-based layout, with **each macro moved to its own dedicated `.rs` file**:
- - **`attr/`** — Attribute macro implementations (e.g., `#[chain]`, `#[renderer]`, `#[help]`, `#[completion]`, `#[dispatcher_clap]`, `#[program_setup]`)
- - **`derive/`** — Derive macro implementations (e.g., `#[derive(Grouped)]`, `#[derive(EnumTag)]`)
- - **`func/`** — Function-like macro implementations (e.g., `pack!`, `group!`, `dispatcher!`, `suggest!`, `entry!`, `node!`, `gen_program!` and its sub-macros)
+ - **`attr/`** — Attribute macro implementations (e.g., `#[chain]` → `attr/chain.rs`, `#[renderer]` → `attr/renderer.rs`, `#[help]` → `attr/help.rs`, `#[completion]` → `attr/completion.rs`, `#[dispatcher_clap]` → `attr/dispatcher_clap.rs`, `#[program_setup]` → `attr/program_setup.rs`)
+ - **`derive/`** — Derive macro implementations (e.g., `#[derive(Grouped)]` → `derive/grouped.rs`, `#[derive(EnumTag)]` → `derive/enum_tag.rs`)
+ - **`func/`** — Function-like macro implementations, each in its own file (e.g., `pack!` → `func/pack.rs`, `group!` → `func/group.rs`, `dispatcher!` → `func/dispatcher.rs`, `suggest!` → `func/suggest.rs`, `entry!` → `func/entry.rs`, `node!` → `func/node.rs`, `gen_program!` → `func/gen_program.rs`, and its sub-macros each in separate files)
- **`systems/`** — Cross-cutting systems (e.g., resource injection, dispatch tree generation, structural data derive support)
- **`extensions/`** — Extension point mechanism for attribute macros (unchanged)
- **`utils.rs`** — Shared utility module for future common helpers
@@ -126,6 +1167,60 @@ None
_No behavioral changes — all existing functionality is preserved. Downstream code that ignores the return value continues to work without modification._
+6. **[`core`]** **`StructuralData` trait now takes a generic parameter `C`.** The `StructuralData` trait and its sealed supertrait `StructuralDataSealed` (both under `::mingling::__private`) have been made generic over a program collector type `C: ProgramCollect<Enum = C>`. This change is necessary for `group_structural!` to bypass the orphan rule — by tying `StructuralData<C>` to `crate::ThisProgram` (which is defined in the user's crate), external types can implement `StructuralData<crate::ThisProgram>` without violating coherence.
+
+ **Migration guide (only relevant for manual `StructuralData` implementations):**
+
+ - All `impl StructuralData for MyType` must be updated to `impl StructuralData<crate::ThisProgram> for MyType`.
+ - All `impl StructuralDataSealed for MyType` must be updated to `impl StructuralDataSealed<crate::ThisProgram> for MyType`.
+ - All trait bounds `T: StructuralData` must be updated to `T: StructuralData<C>` with an additional `C: ProgramCollect<Enum = C>` bound.
+ - The `StructuralRenderer::render` method signature has changed from `render<T: StructuralData + Send>(...)` to `render<T, C>(...) where T: StructuralData<C> + Send, C: ProgramCollect<Enum = C>`.
+
+ **Internal changes:**
+
+ - `StructuralDataSealed` in `mingling_core::__private` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`.
+ - `StructuralData` in `mingling_core::renderer::structural::structural_data` now takes a `C` type parameter with `C: ProgramCollect<Enum = C>`.
+ - Both traits remain under `::mingling::__private`, so this change does **not** affect the public API surface.
+ - `StructuralRenderer::render` now takes an additional generic parameter `C: ProgramCollect<Enum = C>`.
+ - All derive macro and `pack_structural!` / `pack_err_structural!` / `group_structural!` implementations have been updated to emit `impl StructuralDataSealed<crate::ThisProgram>` and `impl StructuralData<crate::ThisProgram>` instead of the non-generic form.
+ - Test code has been updated to use `MockProgramCollect` where appropriate, and integration tests now use `crate::ThisProgram` and call `gen_program!()`.
+
+ _No behavioral changes — this is purely a type-system refactoring to enable `group_structural!` to work with external types. Since both traits are defined in `::mingling::__private`, this change has **no impact on the public API** — end users interact with `StructuralData` only through auto-generated derive macros and `pack_structural!`/`group_structural!` macros, which are automatically updated. Only users with manual `impl StructuralData` blocks (an advanced/rare case) need to update their code.
+
+7. **[`core`]** **`RenderResult` now derives `Clone` and `Eq` in addition to `Default`, `Debug`, and `PartialEq`.** Added `Clone` and `Eq` derive macros to the `RenderResult` struct in `mingling_core/src/renderer/render_result.rs`. These additions enable `RenderResult` values to be explicitly cloned and support equality comparisons that are both reflexive and transitive.
+
+ - **`Clone`** — Allows a `RenderResult` to be duplicated via `.clone()`, which is useful for scenarios where the same render output needs to be reused or stored in multiple locations.
+ - **`Eq`** — Enables `RenderResult` to be used in contexts that require full equivalence (e.g., `assert_eq!` with `Eq` bounds, `HashMap`/`HashSet` keys when combined with `Hash`).
+
+ _No migration is required — these are purely additive derives that expand the type's capabilities without affecting existing behavior._
+
+8. **[`core`]** Added the `build` feature (renamed from `builds`) to `mingling_core` and `mingling`. The old `builds` feature has been deprecated in favor of `build`, with a backward-compatibility alias retained in `mingling/Cargo.toml`:
+
+- **`mingling_core/Cargo.toml`**: Renamed the feature from `builds` to `build`.
+- **`mingling/Cargo.toml`**: Changed the feature dependency from `mingling_core/builds` to `mingling_core/build`. A deprecated `builds` feature alias is kept as `builds = ["mingling_core/build"]` with a note indicating it will be removed in a future breaking change.
+
+ _No behavioral changes — the `build` feature provides identical functionality to the old `builds` feature. Downstream code using `builds` continues to work via the alias, but should migrate to `build`._
+
+9. **[`core`]** Renamed `ResourceMarker` methods from public names (`res_clone`, `res_default`, `modify`) to doc-hidden internal names (`__resource_marker_clone`, `__resource_marker_default`, `__resource_marker_modify`). These methods are internal implementation details of the resource injection system and should not be called directly by user code. By prefixing with `__` and adding `#[doc(hidden)]`, they are still technically accessible but hidden from documentation and tooling, reducing API surface confusion.
+
+ - **`res_clone()`** → **`__resource_marker_clone()`** — Internal method for cloning a resource value during resource injection.
+ - **`res_default()`** → **`__resource_marker_default()`** — Internal method for creating a default resource value during resource injection.
+ - **`modify<C>()`** → **`__resource_marker_modify<C>()`** — Internal method for in-place modification of a resource during resource injection.
+
+ All internal usages within `global_resource.rs` and `lazy_resource.rs` have been updated to use the renamed methods. Test code has been updated accordingly.
+
+ A new module `mingling_core::asset::core_invokes` has been added to provide a centralized location for internal invocation helpers.
+
+10. **[`core:exec`]** Refactored the program execution pipeline (`exec` and `exec_with_args`) to use the `might_be_async` crate instead of manual `#[cfg(feature = "async")]` duplication. The previously separate sync and async implementations have been consolidated into a single `#[might_be_async::func]`-annotated function, with `might_be_async::invoke!()` wrapping the `C::do_chain(current)` call inside `exec_with_args` and the delegation from `exec` to `exec_with_args`.
+
+ The `exec` function no longer contains the full execution loop inline. Instead, it delegates to `exec_with_args` (which now also carries the `#[might_be_async::func]` annotation), reducing code duplication and centralizing the execution logic.
+
+ - **`exec`**: Changed from separate `#[cfg(feature = "async")]` and `#[cfg(not(feature = "async"))]` implementations to a single `#[might_be_async::func]` function that calls `might_be_async::invoke!(exec_with_args(program, &program.args))`.
+ - **`exec_with_args`**: Changed from separate implementations to a single `#[might_be_async::func]` function. The `C::do_chain(current)` call is now wrapped with `might_be_async::invoke!(C::do_chain(current))` to support both sync and async chain execution.
+ - **Removed**: The `error.rs` submodule import remains, but the separate sync/async code blocks in the function bodies have been eliminated.
+
+ _No behavioral changes. All existing functionality — hooks, help handling, chain execution, renderer dispatch, and exit code management — is preserved identically._
+
#### Features:
1. **[`core`]** Added `RenderResult::new()` method for creating a new `RenderResult` with default values (empty text and exit code 0). This provides a more explicit and discoverable constructor compared to `RenderResult::default()`, making it clearer when a fresh result is being created for use with `write!`/`writeln!`.
@@ -302,7 +1397,7 @@ None
}
```
- The `#[routeify]` macro is feature-gated behind `extra_macros` and re-exported as `mingling::macros::routeify`.
+ The `#[routeify]` macro is feature-gated behind `extras` and re-exported as `mingling::macros::routeify`.
Internal changes:
- Added `mingling_macros/src/extensions/routeify.rs` with `routeify_impl` implementation.
@@ -338,6 +1433,107 @@ None
Under the hood, `r_append!` (when used in implicit buffer mode inside a `#[buffer]` function) calls `append_other` on the current render buffer. The `From<F>` implementation allows the buffer function's return value (a `RenderResult`) to be seamlessly converted into the parent's buffer via `append_other`. This enables clean separation of render logic into reusable buffer functions that can be composed together.
+14. **[`core`]** **[`macros`]** Added the `render_route!` macro and `#[renderify]` extension attribute macro, providing error routing to the rendering pipeline (as opposed to `route!`/`#[routeify]` which route to the chain pipeline).
+
+ The `render_route!` macro is conceptually similar to `route!`, but instead of routing errors through `Routable::to_chain()` (returning `ChainProcess`), it routes them directly to the renderer via `crate::ThisProgram::render(AnyOutput::new(e))` (returning `RenderResult`). This makes it suitable for use in `#[renderer]` and `#[help]` functions where the return type is `RenderResult`.
+
+ ```rust,ignore
+ use mingling::macros::{renderer, render_route};
+
+ #[renderer]
+ fn render_something(prev: SomeType) -> RenderResult {
+ let data = render_route!(fetch_data().map_err(|e| ErrorEntry::new(e.to_string())))?;
+ // ... render data
+ Ok(RenderResult::new())
+ }
+ ```
+
+ The `#[renderify]` extension attribute is the rendering-pipeline counterpart to `#[routeify]`. It transforms `expr?` into `render_route!(expr)` (instead of `route!(expr)`), enabling concise error routing in renderer and help functions using the `?` operator syntax.
+
+ ```rust,ignore
+ #[renderer(renderify)]
+ fn render_greeting(prev: Greeting) -> RenderResult {
+ let data = load_data()?; // expands to render_route!(load_data())
+ r_println!("{data}");
+ Ok(RenderResult::new())
+ }
+ ```
+
+ The `#[renderify]` macro can be used:
+ - **Standalone** — as a direct attribute: `#[renderify] fn render(...) { ... }`
+ - **As an extension** — via the extension point system: `#[renderer(renderify)] fn render(...) { ... }` or `#[help(renderify)] fn help(...) { ... }`
+
+ When used as a renderer/help extension, the `renderify` identifier is detected by the extension point mechanism, stripped from the attribute arguments, and `#[renderify]` is applied as an outer attribute on top of `#[renderer]`/`#[help]` — just like `routeify` works with `#[chain]`.
+
+ Both `render_route!` and `#[renderify]` are feature-gated behind `extras` and re-exported as `mingling::macros::render_route` and `mingling::macros::renderify` respectively.
+
+ Internal changes:
+ - Added `mingling_macros/src/extensions/renderify.rs` with `renderify_impl` implementation.
+ - Registered `#[proc_macro] pub fn render_route` and `#[proc_macro_attribute] pub fn renderify` in `mingling_macros/src/lib.rs`.
+
+15. **[`macros`]** Added the `#[mlint(...)]` marker attribute macro — a no-op attribute that passes its attached item through unchanged. The attribute content is ignored by `rustc` and reserved for the Mingling lint (`mlint`) tooling system.
+
+ The `#[mlint]` attribute is registered as a `#[proc_macro_attribute]` and re-exported as `mingling::macros::mlint`. It supports three styles of lint configuration:
+
+ ```rust,ignore
+ #[mlint(allow(MLINT_SOME_LINT))]
+ #[mlint(warn(MLINT_SOME_LINT))]
+ #[mlint(deny(MLINT_SOME_LINT))]
+ fn some_item() {}
+ ```
+
+ Since the attribute is a no-op at compile time, it has no effect on code generation, type checking, or runtime behavior. Its purpose is to serve as a structured annotation that `mlint` tooling can parse from the AST. The attribute is feature-gated behind `extras`.
+
+16. **[`core`]** Added `RendererInvoker<T, C>` and `ChainInvoker<T, C>` types to `mingling_core::asset::core_invokes`, providing a mechanism for selectively invoking renderer and chain pipelines for specific types from within chain/renderer functions via resource injection.
+
+ These types are designed to be created **only** through the resource injection system (via `ResourceMarker::__resource_marker_default`), and attempting to invoke them without being properly injected will panic. They are marked `#[non_exhaustive]` with private fields, so they cannot be constructed by user code.
+
+ ### `RendererInvoker<T, C>`
+
+ Allows invoking the renderer pipeline for a specific type `T`. Use cases include:
+ - **Reusing** — calling another renderer's output from within a `#[renderer]` or `#[chain]` function
+ - **Bypassing** — directly rendering intermediate values without going through the chain pipeline
+
+ ```rust,ignore
+ #[renderer(buffer)]
+ fn render_foo(_: ResultFoo, renderer: &RendererInvoker<Bar>) {
+ let bar = Bar::default();
+ r_append!(renderer.invoke(bar));
+ }
+ ```
+
+ **Methods:**
+ - `invoke(&self, value: T) -> RenderResult` — Invokes the renderer for value `T`, returning the rendered output. Does **not** execute program hooks (by design — this is for bypassing/reusing, not flow control).
+
+ ### `ChainInvoker<T, C>`
+
+ Allows executing chain steps for a specific type `T`. Use cases include:
+ - **Sub-routing** — dispatching to a sub-chain from within a chain handler
+ - **Re-entering** — re-processing a value through the chain pipeline
+
+ ```rust,ignore
+ #[chain]
+ fn handle_foo(_: EntryFoo, chain: &ChainInvoker<StateBar>) -> Next {
+ let bar = Bar::default();
+ // Execute one step of the chain
+ let next = chain.invoke_once(bar);
+ // ... handle the result
+ next
+ }
+ ```
+
+ **Methods:**
+ - `invoke_once(&self, value: T) -> ChainProcess<C>` — Executes a **single step** of chain processing for value `T`. If no chain exists for the type, it converts the value into a `ChainProcess::Ok` with `NextProcess::Chain` routing. Does **not** execute program hooks.
+ - `invoke_to_last(&self, value: T) -> ChainProcess<C>` — Continuously executes the chain for value `T` until it is routed to a renderer or can no longer continue. Each step calls `C::do_chain(any)`. If a step produces a `ChainProcess::Ok` with `NextProcess::Chain` and the next type has no chain handler, it stops and returns that state. Non-chain results (e.g., `ChainProcess::Err`) are returned immediately.
+ - `invoke_to_result(&self, value: T) -> RenderResult` — Convenience method that runs the chain to completion via `invoke_to_last` and then renders the final result. If the final result lacks a renderer or is an error, returns an empty `RenderResult`.
+
+ Both types implement `ResourceMarker` (via `__resource_marker_clone`, `__resource_marker_default`, and `__resource_marker_modify`) which:
+ - `__resource_marker_default` creates instances with `create_by_res_injection: true`, allowing invocation.
+ - `__resource_marker_clone` preserves the `create_by_res_injection` flag.
+ - `__resource_marker_modify` is a no-op (these invokers are not meant to be modified at runtime).
+
+ The types are re-exported from `mingling_core` and exposed to the `mingling` crate root.
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros from being implicitly injected by `#[renderer]` and `#[help]` macros. These macros still exist, but must now be used **explicitly** — either with an explicit buffer argument, or via the `#[buffer]` extension attribute that re-enables implicit buffer injection.
@@ -477,6 +1673,41 @@ None
All examples and internal usages have been updated across the codebase to reflect these changes (e.g., `repl_basic_setup` now calls `println!("{}", r.result)` instead of `println!("{}", r.result.trim())`, since `Display` no longer adds a trailing newline).
+7. **[`any`]** **[`macros`]** Made `AnyOutput`'s `type_id` and `member_id` fields private (`pub(crate)`) and added public accessor methods `type_id()` and `member_id()`. Added the `unsafe fn new_bare<T>(value: T, member_id: G) -> Self` constructor that bypasses the `Grouped` trait, allowing manual specification of `member_id` without requiring the concrete type to implement `Grouped`.
+
+ - **`type_id`** field changed from `pub` to `pub(crate)` — accessible via `type_id()` accessor.
+ - **`member_id`** field changed from `pub` to `pub(crate)` — accessible via `member_id()` accessor (requires `G: Copy`).
+ - **`new_bare`** — Unsafe constructor that takes a raw `member_id` value without invoking `Grouped::member_id()`. The caller must ensure the provided `member_id` correctly corresponds to the concrete type `T`.
+ - Updated all internal `match any.member_id { ... }` patterns in `gen_program.rs` to use `match any.member_id() { ... }` instead.
+ - Updated the panic message in `do_chain` (both sync and async) from `any.type_id` to `any.type_id()`.
+ - Updated the example-hook `main.rs` to call `info.output.member_id()` instead of accessing `info.output.member_id` directly.
+ - Added `Copy` derive to the generated enum to enable `member_id()`'s `Copy` requirement on the enum type.
+
+ _No behavioral changes for existing code — the accessor methods provide the same values as the previously-public fields._
+
+8. **[`any`]** **[`macros`]** **[BREAKING]** Marked `Grouped` trait as `unsafe trait`. The `Grouped` trait has always been inherently unsafe — the `member_id()` return value must exactly correspond to the variant registered by `register_type!` for the concrete type, otherwise dispatching on that type will result in **undefined behavior**. This unsoundness has existed since the trait's inception but was previously unenforced at the type system level.
+
+ By making `Grouped` an `unsafe trait`, implementors must now explicitly acknowledge this safety contract with `unsafe impl Grouped<...> for ...`. This change makes the existing safety invariant visible to developers and enables soundness warnings at compile time.
+
+ **Changes made:**
+
+ - **`Grouped` trait** in `mingling_core/src/any/group.rs` changed from `pub trait Grouped<Group>` to `pub unsafe trait Grouped<Group>`, with a safety doc comment explaining that manually implementing the trait with an incorrect `member_id` leads to undefined behavior.
+
+ - **Derive macros** (`#[derive(Grouped)]`, `#[derive(GroupedSerialize)]`) now generate `unsafe impl` instead of `impl`, with a SAFETY comment stating that the derive macro guarantees correctness because the `Ident` used in `register_type!` matches the `Ident` returned by `member_id()`.
+
+ - **`pack!`, `pack_structural!`, `group!`, `group_structural!`** macros now generate `unsafe impl` instead of `impl`, with analogous SAFETY comments.
+
+ - **All manual test implementations** of `Grouped` across the codebase (in `any.rs` tests, `hook.rs` tests, `mock.rs`) updated to `unsafe impl` with SAFETY comments explaining why they are safe in their test contexts.
+
+ - **`MockProgramCollect::member_id()`** changed from `MockProgramCollect::Foo` to `panic!("Attempting to read an unsafe enum type")` to prevent accidental execution in production paths.
+
+ **Migration guide:**
+
+ - Existing code that uses `Grouped` only through the derive macro or `pack!`/`group!` macros is automatically migrated — no changes needed.
+ - Code with **manual** `impl Grouped<...> for ...` blocks must add `unsafe` before `impl` and verify that the `member_id()` return value correctly corresponds to the type's registered variant. Only proceed if the correspondence is guaranteed.
+
+ _This is a breaking change only for code with manual `Grouped` implementations._
+
---
## Release 0.2.2 (2026-07-10)
@@ -564,7 +1795,7 @@ None
- `test-basic`: Basic type tests with default features (Node, Flag, RenderResult, NextProcess, StringVec)
- `test-comp`: ShellContext, Suggest, SuggestItem, is_completing with `comp + builds` features
- `test-structural-renderer`: StructuralRenderer output in various formats with `structural_renderer_full + parser` features
- - `test-repl`: ResREPL and basic types with `repl + extra_macros` features
+ - `test-repl`: ResREPL and basic types with `repl + extras` features
- `test-dispatch-tree`: Basic types with `dispatch_tree` feature
- `test-all`: Comprehensive testing with all feature combinations (ShellContext, Suggest, ResREPL, StructuralRenderer, Hooks, basic types, etc.)
@@ -728,7 +1959,7 @@ impl ErrorNotDir {
}
```
-This macro is only available with the `extra_macros` feature.
+This macro is only available with the `extras` feature.
9. **[`mingling`]** Added `Groupped` trait to the `mingling::prelude` module, so it can now be imported via `use mingling::prelude::*` without needing to separately import the trait from the `mingling` crate root.
@@ -747,7 +1978,7 @@ An aliased syntax is also supported for descriptive variant naming:
group!(IoError = std::io::Error);
```
-This macro is only available with the `extra_macros` feature.
+This macro is only available with the `extras` feature.
11. **[`macros`]** `#[help]` and `#[completion]` now support resource injection parameters, consistent with `#[chain]` and `#[renderer]`. Specific changes:
@@ -1138,7 +2369,7 @@ fn render(prev: Previous) { // Implicitly introduces `__renderer_inner_result`
}
```
-5. **[`macros`]** Moved the `entry!`, `route!`, `#[program_setup]` macros into the `extra_macros` feature
+5. **[`macros`]** Moved the `entry!`, `route!`, `#[program_setup]` macros into the `extras` feature
6. **[`macros`]** The `crate::Next` generated by `gen_program!()` now requires explicit import into the project