Repository: elastio/bon (language: rust)

Issue:
Is changing `String` to `Into<String>` correct?
When using `#[builder]` on a function, `String` arguments are turned into `Into<String>`. This feels wrong — `String` may have been an intentional choice by the author to, for instance, force allocations to be easily visible. Changing it to `Into<String>` defeats the purpose of that.

I think it's perfectly fine, and even correct, for callers to have to call `.into()` on their strings, just as if they had called the function using normal Rust syntax.


Discussion / hints:
Thank you for bringing this up! This is definitely a controversial topic because of the confrontation between high-level non-performance-sensitive code and low-level performance-sensitive code. This confrontation influences the default behaviour of `bon` and its current choice of `Into<T>`.

Also, I'd like to make it clear from the beginning, that `String` is not a special case. Implicit `Into` cab be added to any setter based on a [particular set of rules](https://elastio.github.io/bon/docs/guide/into-conversions#types-that-qualify-for-an-automatic-into-conversion).


**Advantages** of implicit `Into<String>`:
- It reduces the amount of boilerplate `.into()` or `.to_owned()` or `.to_string()` calls at the call site. The main use case for this is passing `"string literals"` directly to the setter.
- Suppose you have an enum of two variants `A(TypeA)` and `B(TypeB)`. This enum implements `From<TypeA>` and `From<TypeB>`. When such an enum is used in a setter with `Into<Enum>` it's possible to pass the value `TypeA` or `TypeB` directly without having to wrap them into an enum explicitly (which reduces boilerplate).
- I often switch between `&str` and `String` types in my code due to various reasons. Having the setter accept any of them makes it easier to iterate on code and easier to change it.

**Disadvantages** of implicit `Into<String>`
- Hides allocations from the reader. A potential footgun is that passing `&String` as `Into<String>` will also perform an implicit clone of that value.
- Makes the signature a bit more complex to read
- Makes the setter generic and thus complicates type inference in certain situations for the compiler. For example, if the the method accepts an `Option<impl Into<T>>` then you can't pass just `None` to such a setter, you'd need to specify the type `T` for the `None` literal, for example `None::<T>` for the compiler to know what concrete `impl Into<T>` was meant for the `None` literal.

### We can not chose just one of the approaches

In high-level code people usually don't care about allocations that much. Ergonomics of the `bon` syntax is preferred more here. For example, when `bon` is used to define an API function that invokes a REST HTTP API call, then that has many string parameters, then it'd be much more convenient to fill all those string parameters without the need to call `.into()`, `.to_owned()` or `.to_string()` on `&str` literals.

In low-level code, the opposite is true. Every allocation matters and people need to see them with their eyes, because one such implicit allocation may be the performance bottleneck. It's fine to write more verbose code in this case in the sake of better performance.

### Current status quo

I admit, `bon` was designed with some bias towards the high-level kind of code because I've been writing this kind of code at my work. In that context, I know from experience how many `.into()/.to_owned()` calls I and my team have been writing where performance doesn't actually matter.

However, I don't want `bon` to be focused only on high-level code. I'd like `bon` to be useful for low-level code as well, but still, the default behaviour needs to make some preference for only one of the classes of code (high-level or low-level) because there is just one default behaviour available.

Since I've already made that choice in the current `1.0` release of `bon`, we can take it for granted that the default behaviour of `bon` for `Into` conversions leans towards high-level code.

Now, what can we have to satisfy the needs of the low-level code? Today there is already a way to disable the implicit `Into` conversions with `#[builder(into = false)]` on the setter (see the [reference for this attribute](https://elastio.github.io/bon/docs/reference/builder#into)).

However, having to specify `#[builder(into = false)]` on every setter creates a lot of boilerplate too. I've been thinking of adding the ability to set `#[builder(into = false)]` at the top-level of the struct or function where the `#[builder]` attribute was placed to override the default behaviour of automatic `Into` for all setters at once.

**Example of potential new `#[builder]` API:**
```rust
use bon::builder;

// Disables automatic `Into` for all setters
#[builder(into = false)]
struct Example {
    foo: String,
    bar: String,

    // Overrides the top-level `into = false`
    // The setter **will** have an `Into` conversion
    #[builder(into)]
    baz: String,
}

Example::builder()
    // These setters accept `String`
    .foo("foo".to_owned())
    .bar("bar".into())
    // This setter accepts `impl Into<String>`
    .baz("doesn't need an explicit `into` or `to_owned()`")
    .build();
```

Do you think this solution would satisfy this requirement for low-level code and your requirements in particular?

I just think that because rust already gives function authors the ability to make their arguments `impl Into<T>`, there is no need for bon to second guess their intent and turn places where they didn't write that into `impl Into<T>` as well, even if it is more ergonomic for callers. If bon is to do automatic `impl Into<T>` conversion — and I'm not even sure it should — I definitely think it should not be the default. It might even belong in a separate crate, though. 
> even if it is more ergonomic for callers
>  might even belong in a separate crate, though.

This is the main thing. The `bon` crate is this exact crate that does this. One of the core pillars and goals of `bon` is "Great ergonomics" and "Zero boilerplate" (they are mentioned on the [homepage](https://elastio.github.io/bon/)). Unfortunately, removing automatic `Into` conversion will go against this. I can only propose a solution with the configuration to explicitly disable this behaviour as mentioned at the end of my comment https://github.com/elastio/bon/issues/15#issuecomment-2257437916.

Also, note that `Into<String>` doesn't actually imply runtime overhead and worse performance per se. `impl From<T> for T` is a [zero overhead](https://doc.rust-lang.org/1.80.0/src/core/convert/mod.rs.html#765-771) identity function, so if you pass `String` to the setter that accepts `impl Into<String>` then it will do no additional allocations. 
Perhaps you could enable a 'no into' feature in the cargo.toml which would disable this throughout the crate? This might be good because people who don't want the impl Into functionality probably won't want it for anything in the crate anyway
Unfortunatelly, cargo features don't work that way. They must be additive because they undergo [feature unification](https://doc.rust-lang.org/cargo/reference/features.html#feature-unification) between all the crates in the dependency tree. There can't be one crate that uses `bon` with `into` feature disabled and other crate that uses `into` feature enabled and they see entirely different function signatures. All crates in the dependency tree must be compatible with a single set of features of `bon`. It means `bon` with `into` feature enabled must be fully compatible with `bon` without such feature enabled. It will be kinda compatible in most cases, but `impl Into` in the function signature influences type inference, which may lead to compile errors for code that was written against bon without the `into` feature.
Another possibility. `bon` may expose a separate version of the `builder` macro under a different module name. For example

```rust
use bon::explicit::builder;
```

It would expose an "explicit" version of the `builder` macro that doesn't have automatic `into` conversions. This way the syntax will be simpler for the use case where automatic into conversions aren't desired. You just need to import `builder` from the `explicit` module and you won't need to set `#[builder(into = false)]` on each macro usage. 

The naming is debatable though.

However, I'd like to be sure that this is the thing that will be used a lot, such that it really merits such a separate symbol in `bon` crate.
I'm revisiting this issue again. The problem with `impl Into` that's been biting me lately is the weakened type inference.
For example, if there is a member of type `Config` and you try to do `.member(serde_json::from_str(str)?)`, then you have to explicitly specify the type hint `serde_json::from_str::<Config>(str)?` because the parameter's type is `impl Into<Config>`, which could be any type.

Another downside of this - the complexity of the signature. It becomes harder to read it, and this indeed's been biting me as well. Also having to learn the rules of auto-into conversions is another aspect of `bon` that makes it harder to adopt. It's quite easy to shoot yourself in the foot with this.

Given your feedback as well, I'm more inclined to disable automatic `Into` conversions by default. Maybe it's better to do a `2.0` update earlier than later while there isn't too much code using `bon` yet.


Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 30689da6..f2c83ef7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -98,6 +98,32 @@ jobs:
 
       - run: cargo clippy --all-features --all-targets --locked
 
+  cargo-clippy-unstable:
+    runs-on: ubuntu-latest
+
+    # This job is optional to pass. It notifies us about the potential breakages
+    # in the future Rust toolchain versions.
+    continue-on-error: true
+
+    strategy:
+      fail-fast: false
+      matrix:
+        toolchain: [beta, nightly]
+
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions-rust-lang/setup-rust-toolchain@v1
+        with:
+          toolchain: ${{ matrix.toolchain }}
+          components: clippy
+
+      - run: cargo +${{ matrix.toolchain }} clippy --all-features --all-targets --locked
+        env:
+          # There is no need for us to stick with the edition 2021 meaning for
+          # the `expr` fragment in macro rules.
+          RUSTFLAGS: --deny warnings --allow edition-2024-expr-fragment-specifier
+
+
   cargo-doc:
     runs-on: ${{ matrix.os }}-latest
 
diff --git a/.gitignore b/.gitignore
index 011186d1..91128673 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 **/.vitepress/dist/
 articles.json
 *.dbg.s
+.idea/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3ab44264..cf8ad5f3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,1 +1,1 @@
-See the [contributing](https://elastio.github.io/bon/docs/contributing) page on the website for details.
+See the [contributing](https://elastio.github.io/bon/guide/internal/contributing) page on the website for details.
diff --git a/Cargo.lock b/Cargo.lock
index 99dc649d..962e9359 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -38,6 +38,12 @@ version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
 
+[[package]]
+name = "anyhow"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+
 [[package]]
 name = "autocfg"
 version = "1.3.0"
@@ -293,6 +299,7 @@ checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
 name = "e2e-tests"
 version = "1.2.1"
 dependencies = [
+ "anyhow",
  "bon",
  "buildstructor",
  "itertools 0.13.0",
diff --git a/README.md b/README.md
index 9f0e1f49..d5909c92 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-<a href="https://elastio.github.io/bon/docs/guide/overview">
-<!--
-We use an absolute link to the image here because this README is hosted on crates.io
-where this image won't be available through the relative link.
--->
-<img
-    src="https://elastio.github.io/bon/bon-home.png"
-    alt="bon logo"
-/>
+<a href="https://elastio.github.io/bon/guide/overview">
+    <!--
+    We use an absolute link to the image here because this README is hosted on crates.io,
+    lib.rs and docs.rs where this image isn't available through the relative link.
+    -->
+    <img
+        src="https://elastio.github.io/bon/bon-home.png"
+        alt="bon home"
+    />
 </a>
 
 [![crates.io](https://img.shields.io/crates/v/bon.svg?labelColor=orange&color=darkkhaki)](https://crates.io/crates/bon)
@@ -14,11 +14,11 @@ where this image won't be available through the relative link.
 
 `bon` is a Rust crate for generating compile-time-checked builders for functions and structs.
 
-Visit the [guide for a complete overview of the crate](https://elastio.github.io/bon/docs/guide/overview).
+Visit the [guide for a complete overview of the crate](https://elastio.github.io/bon/guide/overview).
 
-## Quick example
+## Quick examples
 
-`bon` can turn a function with positional parameters into a function with "named" parameters via a builder. It's as easy as placing the `#[builder]` macro on top of it.
+`bon` can turn a function with positional parameters into a function with "named" parameters via a builder. It's as easy as placing the `#[builder]` attribute on top of it.
 
 ```rust
 use bon::builder;
@@ -36,13 +36,55 @@ let greeting = greet()
 assert_eq!(greeting, "Hello Bon with age 24!");
 ```
 
-See [the guide](https://elastio.github.io/bon/docs/guide/overview) for the rest.
+You can also use the `#[builder]` attribute with structs and associated methods:
+
+```rust
+use bon::{bon, builder};
+
+#[builder]
+struct User {
+    id: u32,
+    name: String,
+}
+
+#[bon]
+impl User {
+    #[builder]
+    fn greet(&self, target: &str, level: Option<&str>) -> String {
+        let level = level.unwrap_or("INFO");
+        let name = &self.name;
+
+        format!("[{level}] {name} says hello to {target}")
+    }
+}
+
+let user = User::builder()
+    .id(1)
+    .name("Bon".to_owned())
+    .build();
+
+let greeting = user
+    .greet()
+    .target("the world")
+    // `level` is optional, we can omit it here
+    .call();
+
+assert_eq!(user.id, 1);
+assert_eq!(user.name, "Bon");
+assert_eq!(greeting, "[INFO] Bon says hello to the world");
+```
+
+See [the guide](https://elastio.github.io/bon/guide/overview) for the rest.
+
+---
+
+If you like the idea of this crate and want to say "thank you" or "keep doing this" consider giving us a [star ⭐ on Github](https://github.com/elastio/bon). Any support and contribution are appreciated 🐱!
 
 #### License
 
 <sup>
-Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
-2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
+Licensed under either of <a href="https://github.com/elastio/bon/blob/master/LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="https://github.com/elastio/bon/blob/master/LICENSE-MIT">MIT license</a> at your option.
 </sup>
 
 <br>
diff --git a/bon-macros/src/builder/builder_gen/auto_into.rs b/bon-macros/src/builder/builder_gen/auto_into.rs
deleted file mode 100644
index efd8bdcf..00000000
--- a/bon-macros/src/builder/builder_gen/auto_into.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-use super::RegularMember;
-use crate::util::prelude::*;
-use darling::ast::GenericParamExt;
-use std::collections::BTreeSet;
-
-impl super::BuilderGenCtx {
-    // XXX: this behavior is heavily documented in `into-conversions.md`. Please
-    // keep the docs and the implementation in sync.
-    pub(crate) fn member_qualifies_for_into(
-        &self,
-        member: &RegularMember,
-        ty: &syn::Type,
-    ) -> Result<bool> {
-        // User override takes the wheel entirely
-        let Some(user_override) = &member.params.into else {
-            return Ok(self.type_qualifies_for_into(ty));
-        };
-
-        let override_value = user_override.as_ref().value;
-        let default_value = self.type_qualifies_for_into(ty);
-
-        if default_value != override_value {
-            // Override makes sense since it changes the default behavior
-            return Ok(override_value);
-        }
-
-        let maybe_qualifies = if default_value {
-            "qualifies"
-        } else {
-            "doesn't qualify"
-        };
-
-        let member_origin = &member.origin;
-
-        bail!(
-            &user_override.span(),
-            "This attribute is redundant and can be removed. By default the \
-            the type of this {member_origin} already {maybe_qualifies} for `impl Into`.",
-        );
-    }
-
-    pub(crate) fn type_qualifies_for_into(&self, ty: &syn::Type) -> bool {
-        // Only simple type paths qualify for `impl Into`
-        let Some(path) = ty.as_path() else {
-            return false;
-        };
-
-        // <Ty as Trait>::Path projection is too complex
-        if path.qself.is_some() {
-            return false;
-        }
-
-        // Types with generic parameters don't qualify
-        let has_generic_params = path
-            .path
-            .segments
-            .iter()
-            .any(|segment| !segment.arguments.is_empty());
-
-        if has_generic_params {
-            return false;
-        }
-
-        // Bare reference to the type parameter in scope doesn't qualify
-        if let Some(ident) = path.path.get_ident() {
-            let type_params: BTreeSet<_> = self
-                .generics
-                .params
-                .iter()
-                .filter_map(|param| Some(&param.as_type_param()?.ident))
-                .collect();
-
-            if type_params.contains(ident) {
-                return false;
-            }
-        };
-
-        // Do the check for primitive types as the last step to handle the case
-        // when a generic type param was named exactly as one of the primitive types
-        let primitive_types = [
-            "bool", "char", "f32", "f64", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16",
-            "u32", "u64", "u128", "usize",
-        ];
-
-        primitive_types.iter().all(|primitive| {
-            // We check for the last segment name because primitive types may also be referenced
-            // via `std::primitive::{name}` path.
-            !path.path.ends_with_segment(primitive)
-        })
-    }
-}
diff --git a/bon-macros/src/builder/builder_gen/input_func.rs b/bon-macros/src/builder/builder_gen/input_func.rs
index 451a3e93..52f9833b 100644
--- a/bon-macros/src/builder/builder_gen/input_func.rs
+++ b/bon-macros/src/builder/builder_gen/input_func.rs
@@ -287,12 +287,12 @@ impl FuncInputCtx {
             quote::format_ident!("__{}PrivateImpl", builder_ident.raw_name());
         let builder_state_trait_ident = quote::format_ident!("__{}State", builder_ident.raw_name());
 
-        let members: Vec<_> = self
-            .norm_func
-            .sig
-            .inputs
-            .iter()
-            .filter_map(syn::FnArg::as_typed)
+        fn typed_args(func: &syn::ItemFn) -> impl Iterator<Item = &syn::PatType> {
+            func.sig.inputs.iter().filter_map(syn::FnArg::as_typed)
+        }
+
+        let members: Vec<_> = typed_args(&self.norm_func)
+            .zip(typed_args(&self.orig_func))
             .map(Member::from_typed_fn_arg)
             .try_collect()?;
 
@@ -353,6 +353,9 @@ impl FuncInputCtx {
 
         let ctx = BuilderGenCtx {
             members,
+
+            conditional_params: self.params.base.on,
+
             builder_ident,
             builder_private_impl_ident,
             builder_state_trait_ident,
@@ -455,17 +458,18 @@ fn merge_generic_params(
 }
 
 impl Member {
-    pub(crate) fn from_typed_fn_arg(arg: &syn::PatType) -> Result<Self> {
-        let ident = match arg.pat.as_ref() {
+    fn from_typed_fn_arg((norm_arg, orig_arg): (&syn::PatType, &syn::PatType)) -> Result<Self> {
+        let ident = match norm_arg.pat.as_ref() {
             syn::Pat::Ident(pat) => Some(&pat.ident),
             _ => None,
         };
 
         Member::new(
             MemberOrigin::FnArg,
-            &arg.attrs,
+            &norm_arg.attrs,
             ident.cloned(),
-            arg.ty.clone(),
+            norm_arg.ty.clone(),
+            orig_arg.ty.clone(),
         )
     }
 }
diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs
index 4bd59b53..999b82cc 100644
--- a/bon-macros/src/builder/builder_gen/input_struct.rs
+++ b/bon-macros/src/builder/builder_gen/input_struct.rs
@@ -79,19 +79,22 @@ impl StructInputCtx {
 
         let builder_state_trait_ident = quote::format_ident!("__{}State", builder_ident.raw_name());
 
-        let fields = match self.norm_struct.fields {
-            syn::Fields::Named(fields) => fields,
-            _ => {
-                bail!(
-                    &self.norm_struct,
-                    "Only structs with named fields are supported"
-                )
+        fn fields(struct_item: &syn::ItemStruct) -> Result<&syn::FieldsNamed> {
+            match &struct_item.fields {
+                syn::Fields::Named(fields) => Ok(fields),
+                _ => {
+                    bail!(&struct_item, "Only structs with named fields are supported")
+                }
             }
-        };
+        }
+
+        let norm_fields = fields(&self.norm_struct)?;
+        let orig_fields = fields(&self.orig_struct)?;
 
-        let members: Vec<_> = fields
+        let members: Vec<_> = norm_fields
             .named
             .iter()
+            .zip(&orig_fields.named)
             .map(Member::from_syn_field)
             .try_collect()?;
 
@@ -147,6 +150,9 @@ impl StructInputCtx {
 
         let ctx = BuilderGenCtx {
             members,
+
+            conditional_params: self.params.base.on,
+
             builder_ident,
             builder_private_impl_ident,
             builder_state_trait_ident,
@@ -189,12 +195,13 @@ impl FinishFuncBody for StructLiteralBody {
 }
 
 impl Member {
-    pub(crate) fn from_syn_field(field: &syn::Field) -> Result<Self> {
+    fn from_syn_field((norm_field, orig_field): (&syn::Field, &syn::Field)) -> Result<Self> {
         Member::new(
             MemberOrigin::StructField,
-            &field.attrs,
-            field.ident.clone(),
-            Box::new(field.ty.clone()),
+            &norm_field.attrs,
+            norm_field.ident.clone(),
+            Box::new(norm_field.ty.clone()),
+            Box::new(orig_field.ty.clone()),
         )
     }
 }
diff --git a/bon-macros/src/builder/builder_gen/member.rs b/bon-macros/src/builder/builder_gen/member.rs
index 612fb567..e14433a3 100644
--- a/bon-macros/src/builder/builder_gen/member.rs
+++ b/bon-macros/src/builder/builder_gen/member.rs
@@ -1,6 +1,7 @@
+use crate::builder::params::ConditionalParams;
 use crate::util::prelude::*;
 use darling::util::SpannedValue;
-use darling::{FromAttributes, FromMeta};
+use darling::FromAttributes;
 use quote::quote;
 use std::fmt;
 use syn::spanned::Spanned;
@@ -20,6 +21,15 @@ impl fmt::Display for MemberOrigin {
     }
 }
 
+impl MemberOrigin {
+    fn parent_construct(&self) -> &'static str {
+        match self {
+            Self::FnArg => "function",
+            Self::StructField => "struct",
+        }
+    }
+}
+
 #[derive(Debug)]
 pub(crate) enum Member {
     Regular(RegularMember),
@@ -42,8 +52,11 @@ pub(crate) struct RegularMember {
     /// on top of the original member
     pub(crate) docs: Vec<syn::Attribute>,
 
-    /// Type of member that the builder should have setters for.
-    pub(crate) ty: Box<syn::Type>,
+    /// Normalized type of the member that the builder should have setters for.
+    pub(crate) norm_ty: Box<syn::Type>,
+
+    /// Original type of the member (not normalized)
+    pub(crate) orig_ty: Box<syn::Type>,
 
     /// The name of the associated type in the builder state trait that corresponds
     /// to this member.
@@ -58,7 +71,7 @@ pub(crate) struct RegularMember {
 pub(crate) struct SkippedMember {
     pub(crate) ident: Option<syn::Ident>,
 
-    pub(crate) ty: Box<syn::Type>,
+    pub(crate) norm_ty: Box<syn::Type>,
 
     /// Value to assign to the member
     pub(crate) value: SpannedValue<Option<syn::Expr>>,
@@ -67,21 +80,21 @@ pub(crate) struct SkippedMember {
 #[derive(Debug, Clone, darling::FromAttributes)]
 #[darling(attributes(builder))]
 pub(crate) struct MemberParams {
-    /// Overrides the decision to use `Into` for the setter method.
-    pub(crate) into: Option<SpannedValue<StrictBool>>,
+    /// Enables an `Into` conversion for the setter method.
+    pub(crate) into: darling::util::Flag,
 
     /// Assign a default value to the member it it's not specified.
     ///
     /// An optional expression can be provided to set the value for the member,
     /// otherwise its  [`Default`] trait impl will be used.
-    #[darling(with = "parse_optional_expression", map = "Some")]
+    #[darling(with = parse_optional_expression, map = Some)]
     pub(crate) default: Option<SpannedValue<Option<syn::Expr>>>,
 
     /// Skip generating a setter method for this member.
     ///
     /// An optional expression can be provided to set the value for the member,
     /// otherwise its  [`Default`] trait impl will be used.
-    #[darling(with = "parse_optional_expression", map = "Some")]
+    #[darling(with = parse_optional_expression, map = Some)]
     pub(crate) skip: Option<SpannedValue<Option<syn::Expr>>>,
 
     /// Rename the name exposed in the builder API.
@@ -100,7 +113,7 @@ impl MemberParams {
             let other_attr = [
                 default.as_ref().map(|attr| ("default", attr.span())),
                 name.as_ref().map(|attr| ("name", attr.span())),
-                into.as_ref().map(|attr| ("into", attr.span())),
+                into.is_present().then(|| ("into", into.span())),
             ]
             .into_iter()
             .flatten()
@@ -127,33 +140,6 @@ impl MemberParams {
     }
 }
 
-/// This primitive represents the syntax that accepts only two states:
-/// a word e.g. `#[attr(field)]` represents true, and an expression with
-/// `false` e.g. `#[attr(field = false)]` represents false. No other syntax
-/// is accepted. That's why it's called a "strict" bool.
-#[derive(Debug, Clone)]
-pub(crate) struct StrictBool {
-    pub(crate) value: bool,
-}
-
-impl FromMeta for StrictBool {
-    fn from_word() -> Result<Self> {
-        Ok(Self { value: true })
-    }
-
-    fn from_bool(value: bool) -> Result<Self> {
-        if !value {
-            return Ok(Self { value: false });
-        }
-
-        // Error span is set by default trait impl in the caller
-        Err(Error::custom(format_args!(
-            "No need to write `= true`. Just mentioning the attribute is enough \
-            to set it to `true`, so remove the `= true` part.",
-        )))
-    }
-}
-
 fn parse_optional_expression(meta: &syn::Meta) -> Result<SpannedValue<Option<syn::Expr>>> {
     match meta {
         syn::Meta::Path(_) => Ok(SpannedValue::new(None, meta.span())),
@@ -167,7 +153,8 @@ impl Member {
         origin: MemberOrigin,
         attrs: &[syn::Attribute],
         ident: Option<syn::Ident>,
-        ty: Box<syn::Type>,
+        norm_ty: Box<syn::Type>,
+        orig_ty: Box<syn::Type>,
     ) -> Result<Self> {
         let docs = attrs.iter().filter(|attr| attr.is_doc()).cloned().collect();
 
@@ -175,12 +162,16 @@ impl Member {
         params.validate()?;
 
         if let Some(value) = params.skip {
-            return Ok(Self::Skipped(SkippedMember { ident, ty, value }));
+            return Ok(Self::Skipped(SkippedMember {
+                ident,
+                norm_ty,
+                value,
+            }));
         }
 
         let ident = ident.or_else(|| params.name.clone()).ok_or_else(|| {
             err!(
-                &ty,
+                &norm_ty,
                 "can't infer the name to use for this {origin}; please use a simple \
                 `identifier: type` syntax for the {origin}, or add \
                 `#[builder(name = explicit_name)]` to specify the name explicitly",
@@ -191,7 +182,8 @@ impl Member {
             origin,
             state_assoc_type_ident: ident.snake_to_pascal_case(),
             ident,
-            ty,
+            norm_ty,
+            orig_ty,
             params,
             docs,
         };
@@ -203,10 +195,10 @@ impl Member {
 }
 
 impl Member {
-    pub(crate) fn ty(&self) -> &syn::Type {
+    pub(crate) fn norm_ty(&self) -> &syn::Type {
         match self {
-            Self::Regular(me) => &me.ty,
-            Self::Skipped(me) => &me.ty,
+            Self::Regular(me) => &me.norm_ty,
+            Self::Skipped(me) => &me.norm_ty,
         }
     }
 
@@ -230,7 +222,7 @@ impl RegularMember {
         super::reject_self_references_in_docs(&self.docs)?;
 
         if let Some(default) = &self.params.default {
-            if self.ty.is_option() {
+            if self.norm_ty.is_option() {
                 bail!(
                     &default.span(),
                     "`Option<_>` already implies a default of `None`, \
@@ -242,22 +234,25 @@ impl RegularMember {
         Ok(())
     }
 
-    pub(crate) fn as_optional(&self) -> Option<&syn::Type> {
-        self.ty
-            .option_type_param()
-            .or_else(|| (self.params.default.is_some()).then_some(&self.ty))
+    fn as_optional_with_ty<'a>(&'a self, ty: &'a syn::Type) -> Option<&'a syn::Type> {
+        ty.option_type_param()
+            .or_else(|| (self.params.default.is_some()).then_some(ty))
+    }
+
+    pub(crate) fn as_optional_norm_ty(&self) -> Option<&syn::Type> {
+        Self::as_optional_with_ty(self, &self.norm_ty)
     }
 
     pub(crate) fn init_expr(&self) -> TokenStream2 {
-        self.as_optional()
+        self.as_optional_norm_ty()
             .map(|_| quote!(::bon::private::Optional(::core::marker::PhantomData)))
             .unwrap_or_else(|| quote!(::bon::private::Required(::core::marker::PhantomData)))
     }
 
     pub(crate) fn unset_state_type(&self) -> TokenStream2 {
-        let ty = &self.ty;
+        let ty = &self.norm_ty;
 
-        if let Some(inner_type) = self.as_optional() {
+        if let Some(inner_type) = self.as_optional_norm_ty() {
             quote!(::bon::private::Optional<#inner_type>)
         } else {
             quote!(::bon::private::Required<#ty>)
@@ -265,9 +260,9 @@ impl RegularMember {
     }
 
     pub(crate) fn set_state_type_param(&self) -> TokenStream2 {
-        let ty = &self.ty;
+        let ty = &self.norm_ty;
 
-        self.as_optional()
+        self.as_optional_norm_ty()
             .map(|ty| quote!(Option<#ty>))
             .unwrap_or_else(|| quote!(#ty))
     }
@@ -277,4 +272,32 @@ impl RegularMember {
 
         quote!(::bon::private::Set<#ty>)
     }
+
+    pub(crate) fn has_into(&self, conditional_params: &[ConditionalParams]) -> Result<bool> {
+        let scrutinee = self
+            .as_optional_with_ty(&self.orig_ty)
+            .unwrap_or(&self.orig_ty);
+
+        let verdict_from_defaults = conditional_params
+            .iter()
+            .map(|params| Ok((params, scrutinee.matches(&params.type_pattern)?)))
+            .collect::<Result<Vec<_>>>()?
+            .into_iter()
+            .filter(|(_, matched)| *matched)
+            .any(|(params, _)| params.into.is_present());
+
+        let verdict_from_override = self.params.into.is_present();
+
+        if verdict_from_defaults && verdict_from_override {
+            bail!(
+                &self.params.into.span(),
+                "this `#[builder(into)]` attribute is redundant, because `into` \
+                is already implied for this member via the `#[builder(on(...))]` \
+                at the top of the {}",
+                self.origin.parent_construct(),
+            );
+        }
+
+        Ok(verdict_from_override || verdict_from_defaults)
+    }
 }
diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs
index 1ed6b299..e581c76d 100644
--- a/bon-macros/src/builder/builder_gen/mod.rs
+++ b/bon-macros/src/builder/builder_gen/mod.rs
@@ -1,4 +1,3 @@
-mod auto_into;
 mod member;
 mod setter_methods;
 
@@ -7,6 +6,7 @@ pub(crate) mod input_struct;
 
 use member::*;
 
+use super::params::ConditionalParams;
 use crate::util::prelude::*;
 use quote::quote;
 
@@ -28,6 +28,8 @@ pub(crate) struct AssocMethodCtx {
 pub(crate) struct BuilderGenCtx {
     pub(crate) members: Vec<Member>,
 
+    pub(crate) conditional_params: Vec<ConditionalParams>,
+
     pub(crate) generics: Generics,
     pub(crate) vis: syn::Visibility,
     pub(crate) assoc_method_ctx: Option<AssocMethodCtx>,
@@ -81,7 +83,7 @@ impl<'a> MemberExpr<'a> {
     /// the resulting type of the expression, so we add a type hint via an
     /// intermediate variable here.
     fn with_type_hint(member: &'a Member, expr: TokenStream2) -> Self {
-        let ty = member.ty();
+        let ty = member.norm_ty();
         let expr = quote! {{
             let value: #ty = #expr;
             value
@@ -134,6 +136,41 @@ impl BuilderGenCtx {
         self.start_func.generics.as_ref().unwrap_or(&self.generics)
     }
 
+    /// Generates code that has no meaning to the compiler, but it helps
+    /// IDEs to provide better code highlighting, completions and other
+    /// hints.
+    fn ide_hints(&self) -> TokenStream2 {
+        let type_patterns = self
+            .conditional_params
+            .iter()
+            .map(|params| &params.type_pattern)
+            .collect::<Vec<_>>();
+
+        if type_patterns.is_empty() {
+            return quote! {};
+        }
+
+        quote! {
+            // This is wrapped in a special cfg set by `rust-analyzer` to enable this
+            // code for rust-analyzer's analysis only, but prevent the code from being
+            // compiled by `rustc`. Rust Analyzer should be able to use the syntax
+            // provided inside of the block to figure out the semantic meaning of
+            // the tokens passed to the attribute.
+            #[cfg(rust_analyzer)]
+            {
+                // Let IDEs know that these are type patterns like the ones that
+                // could be written in a type annotation for a variable. Note that
+                // we don't initialize the variable with any value because we don't
+                // have any meaningful value to assign to this variable, especially
+                // because its type may contain wildcard patterns like `_`. This is
+                // used only to signal the IDEs that these tokens are meant to be
+                // type patterns by placing them in the context where type patterns
+                // are expected.
+                let _: (#(#type_patterns,)*);
+            }
+        }
+    }
+
     fn start_func(&self) -> syn::ItemFn {
         let builder_ident = &self.builder_ident;
 
@@ -178,6 +215,8 @@ impl BuilderGenCtx {
             }
         });
 
+        let ide_hints = self.ide_hints();
+
         let func = quote! {
             #(#docs)*
             #[inline(always)]
@@ -188,6 +227,8 @@ impl BuilderGenCtx {
             >
             #where_clause
             {
+                #ide_hints
+
                 #builder_ident {
                     __private_impl: #builder_private_impl_ident {
                         _phantom: ::core::marker::PhantomData,
@@ -202,7 +243,7 @@ impl BuilderGenCtx {
     }
 
     fn phantom_data(&self) -> TokenStream2 {
-        let member_types = self.members.iter().map(Member::ty);
+        let member_types = self.members.iter().map(Member::norm_ty);
         let receiver_ty = self
             .assoc_method_ctx
             .as_ref()
@@ -394,27 +435,18 @@ impl BuilderGenCtx {
                     .value
                     .as_ref()
                     .as_ref()
-                    .map(|value| {
-                        let qualified_for_into = self.type_qualifies_for_into(&skipped.ty);
-                        if qualified_for_into {
-                            quote! { ::core::convert::Into::into((|| #value)()) }
-                        } else {
-                            quote! { #value }
-                        }
-                    })
-                    .unwrap_or_else(|| {
-                        quote! { ::core::default::Default::default() }
-                    });
+                    .map(|value| quote! { #value })
+                    .unwrap_or_else(|| quote! { ::core::default::Default::default() });
 
                 return Ok(MemberExpr::with_type_hint(member, expr));
             }
         };
 
         let maybe_default = regular
-            .as_optional()
+            .as_optional_norm_ty()
             // For `Option` members we don't need any `unwrap_or_[else/default]`.
             // We pass them directly to the function unchanged.
-            .filter(|_| !regular.ty.is_option())
+            .filter(|_| !regular.norm_ty.is_option())
             .map(|_| {
                 regular
                     .params
@@ -422,9 +454,8 @@ impl BuilderGenCtx {
                     .as_ref()
                     .and_then(|val| val.as_ref().as_ref())
                     .map(|default| {
-                        let qualified_for_into =
-                            self.member_qualifies_for_into(regular, &regular.ty)?;
-                        let default = if qualified_for_into {
+                        let has_into = regular.has_into(&self.conditional_params)?;
+                        let default = if has_into {
                             quote! { ::core::convert::Into::into((|| #default)()) }
                         } else {
                             quote! { #default }
@@ -439,20 +470,22 @@ impl BuilderGenCtx {
         let member_ident = &regular.ident;
 
         let expr = quote! {
-            ::core::convert::Into::<::bon::private::Set<_>>::into(self.__private_impl.#member_ident)
-                .0
-                #maybe_default
+            ::core::convert::Into::<::bon::private::Set<_>>::into(
+                self.__private_impl.#member_ident
+            )
+            .0
+            #maybe_default
         };
 
         Ok(MemberExpr::with_type_hint(member, expr))
     }
 
     fn finish_method_impl(&self) -> Result<TokenStream2> {
-        let member_exprs: Vec<_> = self
+        let member_exprs = self
             .members
             .iter()
             .map(|member| self.member_expr(member))
-            .try_collect()?;
+            .collect::<Result<Vec<_>>>()?;
 
         let body = &self.finish_func.body.generate(&member_exprs);
         let asyncness = &self.finish_func.asyncness;
diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs
index c13fc0ff..631cf113 100644
--- a/bon-macros/src/builder/builder_gen/setter_methods.rs
+++ b/bon-macros/src/builder/builder_gen/setter_methods.rs
@@ -152,17 +152,15 @@ impl<'a> MemberSettersCtx<'a> {
     }
 
     fn setter_methods(&self) -> Result<TokenStream2> {
-        let member_type = self.member.ty.as_ref();
+        let member_type = self.member.norm_ty.as_ref();
 
-        if let Some(inner_type) = self.member.as_optional() {
+        if let Some(inner_type) = self.member.as_optional_norm_ty() {
             return self.setters_for_optional_member(inner_type);
         }
 
-        let qualified_for_into = self
-            .builder_gen
-            .member_qualifies_for_into(self.member, &self.member.ty)?;
+        let has_into = self.member.has_into(&self.builder_gen.conditional_params)?;
 
-        let (fn_param_type, maybe_into_call) = if qualified_for_into {
+        let (fn_param_type, maybe_into_call) = if has_into {
             (quote!(impl Into<#member_type>), quote!(.into()))
         } else {
             (quote!(#member_type), quote!())
@@ -177,11 +175,8 @@ impl<'a> MemberSettersCtx<'a> {
     }
 
     fn setters_for_optional_member(&self, inner_type: &syn::Type) -> Result<TokenStream2> {
-        let qualified_for_into = self
-            .builder_gen
-            .member_qualifies_for_into(self.member, inner_type)?;
-
-        let (inner_type, maybe_conv_call, maybe_map_conv_call) = if qualified_for_into {
+        let has_into = self.member.has_into(&self.builder_gen.conditional_params)?;
+        let (inner_type, maybe_conv_call, maybe_map_conv_call) = if has_into {
             (
                 quote!(impl Into<#inner_type>),
                 quote!(.into()),
@@ -224,12 +219,10 @@ impl<'a> MemberSettersCtx<'a> {
             },
         ];
 
-        let setters = methods
+        Ok(methods
             .into_iter()
             .map(|method| self.setter_method(method))
-            .collect();
-
-        Ok(setters)
+            .collect())
     }
 
     fn setter_method(&self, method: MemberSetterMethod) -> TokenStream2 {
diff --git a/bon-macros/src/builder/params.rs b/bon-macros/src/builder/params.rs
index e6e873ba..d545b7ea 100644
--- a/bon-macros/src/builder/params.rs
+++ b/bon-macros/src/builder/params.rs
@@ -1,11 +1,98 @@
 use crate::util::prelude::*;
 use darling::FromMeta;
+use proc_macro2::Span;
 use quote::quote;
+use syn::parse::Parse;
+use syn::spanned::Spanned;
+use syn::visit::Visit;
 
 #[derive(Debug, FromMeta)]
 pub(crate) struct BuilderParams {
     pub(crate) finish_fn: Option<syn::Ident>,
     pub(crate) builder_type: Option<syn::Ident>,
+
+    #[darling(multiple)]
+    pub(crate) on: Vec<ConditionalParams>,
+}
+
+#[derive(Debug)]
+pub(crate) struct ConditionalParams {
+    pub(crate) type_pattern: syn::Type,
+    pub(crate) into: darling::util::Flag,
+}
+
+impl Parse for ConditionalParams {
+    fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
+        let type_pattern = input.parse()?;
+
+        let _ = input.parse::<syn::Token![,]>()?;
+        let rest: TokenStream2 = input.parse()?;
+
+        #[derive(FromMeta)]
+        struct Parsed {
+            into: darling::util::Flag,
+        }
+
+        let Parsed { into } = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?;
+
+        if !into.is_present() {
+            return Err(syn::Error::new_spanned(
+                &rest,
+                "this #[builder(on(type_pattern, ...))] contains no options to override \
+                the default behavior for the selected setters like `into`, so it \
+                does nothing",
+            ));
+        }
+
+        struct FindAttr {
+            attr: Option<Span>,
+        }
+
+        impl Visit<'_> for FindAttr {
+            fn visit_attribute(&mut self, attr: &'_ syn::Attribute) {
+                self.attr.get_or_insert(attr.span());
+            }
+        }
+
+        let mut find_attr = FindAttr { attr: None };
+        find_attr.visit_type(&type_pattern);
+        let attr_in_type_pattern = find_attr.attr;
+
+        if let Some(attr) = attr_in_type_pattern {
+            return Err(syn::Error::new(
+                attr,
+                "nested attributes are not allowed in the type pattern of \
+                #[builder(on(type_pattern, ...))]",
+            ));
+        }
+
+        // Validate that the pattern. The validation is done in the process
+        // of matching the types. To make sure that matching traverses the
+        // full pattern we match it with itself.
+        let type_pattern_matches_itself = type_pattern.matches(&type_pattern)?;
+
+        assert!(
+            type_pattern_matches_itself,
+            "BUG: the type pattern does not match itself: {type_pattern:#?}"
+        );
+
+        Ok(Self { type_pattern, into })
+    }
+}
+
+impl FromMeta for ConditionalParams {
+    fn from_meta(meta: &syn::Meta) -> Result<Self> {
+        let syn::Meta::List(meta) = meta else {
+            bail!(
+                meta,
+                "Expected an attribute of form `on(type_pattern, ...)`"
+            );
+        };
+
+        let me = syn::parse2(meta.tokens.clone())?;
+
+        Ok(me)
+    }
 }
 
 #[derive(Debug, Default)]
diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs
index 20453ee0..610f603e 100644
--- a/bon-macros/src/lib.rs
+++ b/bon-macros/src/lib.rs
@@ -9,13 +9,14 @@ mod set;
 mod util;
 
 use proc_macro::TokenStream;
+use syn::parse::Parser;
 
 /// Can be placed on top of a free function or an associated method or a struct
 /// declaration. Generates a builder for the item beneath it.
 ///
-/// There documentation for this macro is split into two parts:
-/// - [Overview page](https://elastio.github.io/bon/docs/guide/overview)
-/// - [Attributes reference](https://elastio.github.io/bon/docs/reference/builder)
+/// Docs for this macro are split into two parts:
+/// - [Guide](https://elastio.github.io/bon/guide/overview)
+/// - [Attributes reference](https://elastio.github.io/bon/reference/builder)
 ///
 /// # Quick example
 ///
@@ -38,15 +39,63 @@ use proc_macro::TokenStream;
 /// assert_eq!(greeting, "Hello Bon with age 24!");
 /// ```
 ///
-/// See the [overview](https://elastio.github.io/bon/docs/guide/overview) for the
-/// rest of the docs about associated methods, structs, and more.
+/// You can also use the `#[builder]` attribute with structs and associated methods:
+///
+/// ```rust ignore
+/// use bon::{bon, builder};
+///
+/// #[builder]
+/// struct User {
+///     id: u32,
+///     name: String,
+/// }
+///
+/// #[bon]
+/// impl User {
+///     #[builder]
+///     fn greet(&self, target: &str, level: Option<&str>) -> String {
+///         let level = level.unwrap_or("INFO");
+///         let name = &self.name;
+///
+///         format!("[{level}] {name} says hello to {target}")
+///     }
+/// }
+///
+/// let user = User::builder()
+///     .id(1)
+///     .name("Bon".to_owned())
+///     .build();
+///
+/// let greeting = user
+///     .greet()
+///     .target("the world")
+///     // `level` is optional, we can omit it here
+///     .call();
+///
+/// assert_eq!(user.id, 1);
+/// assert_eq!(user.name, "Bon");
+/// assert_eq!(greeting, "[INFO] Bon says hello to the world");
+/// ```
+///
+/// See [the guide](https://elastio.github.io/bon/guide/overview) for the rest.
 #[proc_macro_attribute]
 pub fn builder(params: TokenStream, item: TokenStream) -> TokenStream {
-    syn::parse(item.clone())
+    let meta = util::ide::parse_comma_separated_meta
+        .parse2(params.clone().into())
+        .unwrap_or_default();
+
+    let completions = util::ide::generate_completions(meta);
+
+    let main = syn::parse(item.clone())
         .map_err(Into::into)
         .and_then(|item| builder::generate_for_item(params.into(), item))
-        .unwrap_or_else(|err| error::error_into_token_stream(err, item.into()))
-        .into()
+        .unwrap_or_else(|err| error::error_into_token_stream(err, item.into()));
+
+    quote::quote! {
+        #completions
+        #main
+    }
+    .into()
 }
 
 /// Companion macro for [`builder`]. You should place it on top of the `impl` block
@@ -56,7 +105,7 @@ pub fn builder(params: TokenStream, item: TokenStream) -> TokenStream {
 /// inside of the `impl` block. You'll get compile errors without that context.
 ///
 /// For details on this macro including the reason why it's needed see this
-/// paragraph in the [overview](https://elastio.github.io/bon/docs/guide/overview#builder-for-an-associated-method).
+/// paragraph in the [overview](https://elastio.github.io/bon/guide/overview#builder-for-an-associated-method).
 ///
 /// # Quick example
 ///
@@ -144,7 +193,7 @@ pub fn bon(params: TokenStream, item: TokenStream) -> TokenStream {
 /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html
 #[proc_macro]
 pub fn map(input: TokenStream) -> TokenStream {
-    let entries = syn::parse_macro_input!(input with util::parse_map_macro_input);
+    let entries = syn::parse_macro_input!(input with map::parse_macro_input);
 
     map::generate(entries).into()
 }
diff --git a/bon-macros/src/map.rs b/bon-macros/src/map.rs
index 79067f31..73c354a5 100644
--- a/bon-macros/src/map.rs
+++ b/bon-macros/src/map.rs
@@ -1,10 +1,25 @@
 use crate::util;
 use crate::util::prelude::*;
 use quote::quote;
+use syn::parse::ParseStream;
 use syn::punctuated::Punctuated;
 use syn::Expr;
 use syn::Token;
 
+pub(crate) fn parse_macro_input(
+    input: ParseStream<'_>,
+) -> Result<Punctuated<(Expr, Expr), Token![,]>, syn::Error> {
+    Punctuated::parse_terminated_with(input, parse_map_pair)
+}
+
+fn parse_map_pair(pair: ParseStream<'_>) -> Result<(Expr, Expr), syn::Error> {
+    let key = pair.parse().map_err(|_| pair.error(pair))?;
+    let _: Token![:] = pair.parse()?;
+    let value = pair.parse()?;
+
+    Ok((key, value))
+}
+
 pub(crate) fn generate(entries: Punctuated<(Expr, Expr), Token![,]>) -> TokenStream2 {
     let error =
         util::validate_expressions_are_unique("key in the map", entries.iter().map(|(k, _)| k));
diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs
new file mode 100644
index 00000000..f2315249
--- /dev/null
+++ b/bon-macros/src/util/ide.rs
@@ -0,0 +1,251 @@
+// We must make sure this code never panics. It must be able to handle all possible inputs.
+#![deny(
+    clippy::unwrap_used,
+    clippy::expect_used,
+    clippy::panic,
+    clippy::unreachable,
+    clippy::unimplemented,
+    clippy::todo,
+    clippy::exit
+)]
+
+use crate::util::prelude::*;
+use proc_macro2::{Span, TokenTree};
+use quote::quote;
+use syn::parse::{Parse, ParseStream, Parser};
+use syn::{token, Token};
+
+pub(crate) fn parse_comma_separated_meta(input: ParseStream<'_>) -> syn::Result<Vec<Meta>> {
+    let mut output = vec![];
+
+    while !input.is_empty() {
+        let value = input.parse::<Meta>()?;
+
+        output.push(value);
+
+        while !input.is_empty() {
+            if input.peek(Token![,]) {
+                input.parse::<Token![,]>()?;
+                break;
+            }
+
+            // Skip the invalid token (comma is expected only).
+            input.parse::<TokenTree>()?;
+        }
+    }
+
+    Ok(output)
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum Meta {
+    None,
+    Path(syn::Path),
+    List(MetaList),
+    NameValue(MetaNameValue),
+}
+
+impl Parse for Meta {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        let path = loop {
+            let path = input.call(syn::Path::parse_mod_style).ok();
+
+            if let Some(path) = path {
+                break path;
+            }
+
+            if input.parse::<TokenTree>().is_err() {
+                return Ok(Self::None);
+            }
+        };
+
+        let meta = if input.peek(token::Paren) {
+            let content;
+            syn::parenthesized!(content in input);
+
+            Self::List(MetaList {
+                path,
+                tokens: content.parse()?,
+            })
+        } else if input.peek(token::Bracket) {
+            let content;
+            syn::bracketed!(content in input);
+
+            Self::List(MetaList {
+                path,
+                tokens: content.parse()?,
+            })
+        } else if input.peek(token::Brace) {
+            let content;
+            syn::braced!(content in input);
+
+            Self::List(MetaList {
+                path,
+                tokens: content.parse()?,
+            })
+        } else if input.peek(Token![=]) {
+            Self::NameValue(MetaNameValue {
+                path,
+                eq_token: input.parse()?,
+                value: input.parse().ok(),
+            })
+        } else {
+            Meta::Path(path)
+        };
+
+        Ok(meta)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct MetaList {
+    pub(crate) path: syn::Path,
+    pub(crate) tokens: TokenStream2,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct MetaNameValue {
+    pub(crate) path: syn::Path,
+
+    #[allow(dead_code)]
+    pub(crate) eq_token: syn::Token![=],
+
+    #[allow(dead_code)]
+    pub(crate) value: Option<syn::Expr>,
+}
+
+fn paths_from_meta(meta: Vec<Meta>) -> Vec<syn::Path> {
+    meta.into_iter()
+        .filter_map(|meta| match meta {
+            Meta::Path(path) => Some(path),
+            Meta::NameValue(meta) => Some(meta.path),
+            Meta::List(meta) => Some(meta.path),
+            _ => None,
+        })
+        .collect()
+}
+
+/// This is a highly experimental function that attempts to generate code that
+/// can be used by IDEs to provide hints and completions for attributes in macros.
+///
+/// The idea is that we parse a potentially incomplete syntax of the macro invocation
+/// and then we generate a set of modules that contain `use` statements with the
+/// identifiers passed to the macro.
+///
+/// By placing these input identifiers in the right places inside of `use` statements
+/// we can hint the IDEs to provide completions for the attributes based on what's
+/// available in the module the use statement references.
+pub(crate) fn generate_completions(meta: Vec<Meta>) -> TokenStream2 {
+    let completions = CompletionsSchema::with_children(
+        "builder_top_level",
+        vec![
+            CompletionsSchema::leaf("expose_positional_fn"),
+            CompletionsSchema::leaf("start_fn"),
+            CompletionsSchema::leaf("on").set_custom_filter(|meta| {
+                if !meta.is_empty() {
+                    meta.remove(0);
+                }
+            }),
+        ],
+    );
+
+    let completion_triggers = completions.generate_completion_triggers(meta, &[]);
+
+    quote! {
+        // The special `rust_analyzer` CFG is enabled only when Rust Analyzer is
+        // running its code analysis. This allows us to provide code that is
+        // useful only for Rust Analyzer for it to provide hints and completions.
+        #[cfg(rust_analyzer)]
+        const _: () = {
+            #completion_triggers
+        };
+    }
+}
+
+struct CompletionsSchema {
+    key: &'static str,
+    children: Vec<CompletionsSchema>,
+    custom_filter: Option<fn(&mut Vec<Meta>)>,
+}
+
+impl CompletionsSchema {
+    fn leaf(key: &'static str) -> Self {
+        Self {
+            key,
+            children: vec![],
+            custom_filter: None,
+        }
+    }
+
+    fn with_children(key: &'static str, children: Vec<CompletionsSchema>) -> Self {
+        Self {
+            key,
+            children,
+            custom_filter: None,
+        }
+    }
+
+    fn set_custom_filter(mut self, custom_filter: fn(&mut Vec<Meta>)) -> Self {
+        self.custom_filter = Some(custom_filter);
+        self
+    }
+
+    fn generate_completion_triggers(
+        &self,
+        mut meta: Vec<Meta>,
+        module_prefix: &[&syn::Ident],
+    ) -> TokenStream2 {
+        if let Some(custom_filter) = self.custom_filter {
+            custom_filter(&mut meta)
+        };
+
+        let module_suffix = syn::Ident::new(self.key, Span::call_site());
+        let module_name = module_prefix
+            .iter()
+            .copied()
+            .chain([&module_suffix])
+            .collect::<Vec<_>>();
+
+        let child_completion_triggers = self
+            .children
+            .iter()
+            .map(|child| {
+                let child_metas = meta
+                    .iter()
+                    .filter_map(|meta| {
+                        let meta = match meta {
+                            Meta::List(meta) => meta,
+                            _ => return None,
+                        };
+
+                        if !meta.path.is_ident(&child.key) {
+                            return None;
+                        }
+
+                        parse_comma_separated_meta.parse2(meta.tokens.clone()).ok()
+                    })
+                    .concat();
+
+                child.generate_completion_triggers(child_metas, &module_name)
+            })
+            .collect::<Vec<_>>();
+
+        let paths = paths_from_meta(meta);
+        let module_name_snake_case =
+            syn::Ident::new(&module_name.iter().join("_"), Span::call_site());
+
+        quote! {
+            mod #module_name_snake_case {
+                // We separately import everything from the IDE hints module
+                // and then put the `paths` in the `use self::{ ... }` statement
+                // to avoid Rust Analyzer from providing completions for the
+                // `self` keyword in the `use` statement. It works because
+                // `use self::self` is not a valid syntax.
+                use ::bon::private::ide #(::#module_name)* ::*;
+                use self::{ #( #paths as _, )* };
+            }
+
+            #(#child_completion_triggers)*
+        }
+    }
+}
diff --git a/bon-macros/src/util/iterator.rs b/bon-macros/src/util/iterator.rs
index 1b50044e..777319e3 100644
--- a/bon-macros/src/util/iterator.rs
+++ b/bon-macros/src/util/iterator.rs
@@ -1,3 +1,6 @@
+use crate::util::prelude::*;
+use std::fmt::Write;
+
 pub(crate) trait IteratorExt: Iterator + Sized {
     fn try_collect<U, E>(self) -> Result<U, E>
     where
@@ -5,6 +8,72 @@ pub(crate) trait IteratorExt: Iterator + Sized {
     {
         self.collect()
     }
+
+    /// Based on itertools:
+    /// <https://github.com/rust-itertools/itertools/blob/a4a82e4b97eb76687c2a57cdfd2e5343ff507827/src/lib.rs#L2301-L2330>
+    fn join(mut self, sep: &str) -> String
+    where
+        Self::Item: std::fmt::Display,
+    {
+        let Some(first) = self.next() else {
+            return String::new();
+        };
+
+        // estimate lower bound of capacity needed
+        let (lower, _) = self.size_hint();
+        let mut result = String::with_capacity(sep.len() * lower);
+
+        write!(&mut result, "{}", first).unwrap();
+
+        for elt in self {
+            result.push_str(sep);
+            write!(&mut result, "{}", elt).unwrap();
+        }
+
+        result
+    }
 }
 
 impl<I: Iterator> IteratorExt for I {}
+
+pub(crate) trait IntoIteratorExt: IntoIterator + Sized {
+    fn try_equals_with<O>(
+        self,
+        other: O,
+        compare: impl Fn(Self::Item, O::Item) -> Result<bool>,
+    ) -> Result<bool>
+    where
+        O: IntoIterator,
+        O::IntoIter: ExactSizeIterator,
+        Self::IntoIter: ExactSizeIterator,
+    {
+        let me = self.into_iter();
+        let other = other.into_iter();
+
+        if me.len() != other.len() {
+            return Ok(false);
+        }
+
+        for (a, b) in me.zip(other) {
+            if !compare(a, b)? {
+                return Ok(false);
+            }
+        }
+
+        Ok(true)
+    }
+
+    fn concat(self) -> Self::Item
+    where
+        Self::Item: Extend<<Self::Item as IntoIterator>::Item> + IntoIterator + Default,
+    {
+        self.into_iter()
+            .reduce(|mut a, b| {
+                a.extend(b);
+                a
+            })
+            .unwrap_or_default()
+    }
+}
+
+impl<I: IntoIterator> IntoIteratorExt for I {}
diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs
index 527a33e8..b5900f83 100644
--- a/bon-macros/src/util/mod.rs
+++ b/bon-macros/src/util/mod.rs
@@ -5,13 +5,14 @@ mod iterator;
 mod path;
 mod ty;
 
+pub(crate) mod ide;
+
 use prelude::*;
 use proc_macro::TokenStream;
 use std::collections::HashSet;
-use syn::parse::ParseStream;
+use syn::parse::Parser;
 use syn::punctuated::Punctuated;
 use syn::Expr;
-use syn::Token;
 
 pub(crate) mod prelude {
     /// A handly alias for [`proc_macro2::TokenStream`].
@@ -30,6 +31,7 @@ pub(crate) mod prelude {
     pub(crate) use super::attrs::AttributeExt;
     pub(crate) use super::fn_arg::FnArgExt;
     pub(crate) use super::ident::IdentExt;
+    pub(crate) use super::iterator::IntoIteratorExt;
     pub(crate) use super::iterator::IteratorExt;
     pub(crate) use super::path::PathExt;
     pub(crate) use super::ty::TypeExt;
@@ -52,18 +54,41 @@ where
     Ok((params, item))
 }
 
-pub(crate) fn parse_map_macro_input(
-    input: ParseStream<'_>,
-) -> Result<Punctuated<(Expr, Expr), Token![,]>, syn::Error> {
-    Punctuated::parse_terminated_with(input, parse_map_pair)
-}
+/// Utility for parsing with `#[darling(with = ...)]` attribute that allows to
+/// parse an arbitrary sequence of items inside of parentheses. For example
+/// `foo(a, b, c)`, where `a`, `b`, and `c` are of type `T` and `,` is represented
+/// by the token type `P`.
+// This function was used by earlier iterations of our code, but it's not used now
+// However, let's keep it here for future.
+#[allow(dead_code)]
+pub(crate) fn parse_terminated<T, P>(meta: &syn::Meta) -> Result<Punctuated<T, P>>
+where
+    T: syn::parse::Parse,
+    P: syn::parse::Parse,
+{
+    let item = std::any::type_name::<T>();
+    let punct = std::any::type_name::<P>();
+
+    let name = |val: &str| {
+        format!(
+            "'{}'",
+            val.rsplit("::").next().unwrap_or(val).to_lowercase()
+        )
+    };
+
+    let tokens = match meta {
+        syn::Meta::List(meta) => &meta.tokens,
+        _ => bail!(
+            &meta,
+            "expected a list of {} separated by {}",
+            name(item),
+            name(punct),
+        ),
+    };
 
-fn parse_map_pair(pair: ParseStream<'_>) -> Result<(Expr, Expr), syn::Error> {
-    let key = pair.parse().map_err(|_| pair.error(pair))?;
-    let _: Token![:] = pair.parse()?;
-    let value = pair.parse()?;
+    let punctuated = Punctuated::parse_terminated.parse2(tokens.clone())?;
 
-    Ok((key, value))
+    Ok(punctuated)
 }
 
 pub(crate) fn validate_expressions_are_unique<'k, I>(err_label: &str, items: I) -> Option<Error>
diff --git a/bon-macros/src/util/path.rs b/bon-macros/src/util/path.rs
index 8420e08d..0aff0f77 100644
--- a/bon-macros/src/util/path.rs
+++ b/bon-macros/src/util/path.rs
@@ -1,18 +1,9 @@
 pub(crate) trait PathExt {
-    /// Check if the path ends with the given segment.
-    fn ends_with_segment(&self, desired_segment: &str) -> bool;
-
     /// Check if the path starts with the given segment.
     fn starts_with_segment(&self, desired_segment: &str) -> bool;
 }
 
 impl PathExt for syn::Path {
-    fn ends_with_segment(&self, desired_segment: &str) -> bool {
-        self.segments
-            .last()
-            .is_some_and(|last| last.ident == desired_segment)
-    }
-
     fn starts_with_segment(&self, desired_segment: &str) -> bool {
         self.segments
             .first()
diff --git a/bon-macros/src/util/ty/match_types.rs b/bon-macros/src/util/ty/match_types.rs
new file mode 100644
index 00000000..73b10948
--- /dev/null
+++ b/bon-macros/src/util/ty/match_types.rs
@@ -0,0 +1,402 @@
+use crate::util::iterator::IntoIteratorExt;
+use crate::util::prelude::*;
+use syn::spanned::Spanned;
+
+pub(crate) fn match_return_types(
+    scrutinee: &syn::ReturnType,
+    pattern: &syn::ReturnType,
+) -> Result<bool> {
+    match (scrutinee, pattern) {
+        (syn::ReturnType::Default, syn::ReturnType::Default) => Ok(true),
+        (syn::ReturnType::Default, syn::ReturnType::Type(_, pattern)) => {
+            match_types(&syn::parse_quote!(()), pattern)
+        }
+        (syn::ReturnType::Type(_, scrutinee), syn::ReturnType::Default) => {
+            Ok(**scrutinee == syn::parse_quote!(()))
+        }
+        (syn::ReturnType::Type(_, scrutinee), syn::ReturnType::Type(_, pattern)) => {
+            match_types(scrutinee, pattern)
+        }
+    }
+}
+
+fn match_paths(scrutinee: &syn::Path, pattern: &syn::Path) -> Result<bool> {
+    let verdict = scrutinee.leading_colon == pattern.leading_colon
+        && scrutinee
+            .segments
+            .iter()
+            .try_equals_with(&pattern.segments, |scrutinee, pattern| {
+                let verdict = scrutinee.ident == pattern.ident
+                    && match_path_args(&scrutinee.arguments, &pattern.arguments)?;
+
+                Ok(verdict)
+            })?;
+
+    Ok(verdict)
+}
+
+fn match_path_args(scrutinee: &syn::PathArguments, pattern: &syn::PathArguments) -> Result<bool> {
+    use syn::PathArguments::*;
+
+    let verdict = match (scrutinee, pattern) {
+        (None, None) => true,
+        (AngleBracketed(scrutinee), AngleBracketed(pattern)) => {
+            match_angle_bracketed_generic_args(scrutinee, pattern)?
+        }
+        (Parenthesized(scrutinee), Parenthesized(pattern)) => {
+            scrutinee
+                .inputs
+                .iter()
+                .try_equals_with(&pattern.inputs, match_types)?
+                && match_return_types(&scrutinee.output, &pattern.output)?
+        }
+        _ => false,
+    };
+
+    Ok(verdict)
+}
+
+fn match_angle_bracketed_generic_args(
+    scrutinee: &syn::AngleBracketedGenericArguments,
+    pattern: &syn::AngleBracketedGenericArguments,
+) -> Result<bool> {
+    scrutinee
+        .args
+        .iter()
+        .try_equals_with(&pattern.args, match_generic_args)
+}
+
+fn match_option<T>(
+    scrutinee: &Option<T>,
+    pattern: &Option<T>,
+    compare: impl Fn(&T, &T) -> Result<bool>,
+) -> Result<bool> {
+    match (scrutinee, &pattern) {
+        (None, None) => Ok(true),
+        (Some(scrutinee), Some(pattern)) => compare(scrutinee, pattern),
+        _ => Ok(false),
+    }
+}
+
+fn match_generic_args(
+    scrutinee: &syn::GenericArgument,
+    pattern: &syn::GenericArgument,
+) -> Result<bool> {
+    use syn::GenericArgument::*;
+
+    let verdict = match pattern {
+        Lifetime(pattern) => {
+            if pattern.ident != "_" {
+                return Err(unsupported_syntax_error(
+                    pattern,
+                    "Lifetimes are ignored during type pattern matching. \
+                    Use an anonymous lifetime (`'_`) in this position instead. \
+                    Named lifetime syntax in generic parameters",
+                ));
+            }
+
+            matches!(scrutinee, Lifetime(_))
+        }
+        Type(pattern) => {
+            let Type(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            match_types(scrutinee, pattern)?
+        }
+        Const(pattern) => {
+            let Const(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            match_exprs(scrutinee, pattern)
+        }
+        AssocType(pattern) => {
+            let AssocType(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            scrutinee.ident == pattern.ident
+                && match_types(&scrutinee.ty, &pattern.ty)?
+                && match_option(
+                    &scrutinee.generics,
+                    &pattern.generics,
+                    match_angle_bracketed_generic_args,
+                )?
+        }
+        AssocConst(pattern) => {
+            let AssocConst(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+
+            scrutinee.ident == pattern.ident
+                && match_option(
+                    &scrutinee.generics,
+                    &pattern.generics,
+                    match_angle_bracketed_generic_args,
+                )?
+                && match_exprs(&scrutinee.value, &pattern.value)
+        }
+
+        _ => return Err(unsupported_syntax_error(&pattern, "This syntax")),
+    };
+
+    Ok(verdict)
+}
+
+fn match_exprs(scrutinee: &syn::Expr, pattern: &syn::Expr) -> bool {
+    matches!(pattern, syn::Expr::Infer(_)) || scrutinee == pattern
+}
+
+pub(crate) fn match_types(scrutinee: &syn::Type, pattern: &syn::Type) -> Result<bool> {
+    use syn::Type::*;
+
+    let pattern = pattern.peel();
+
+    if let Infer(_) = pattern {
+        return Ok(true);
+    }
+
+    let scrutinee = scrutinee.peel();
+
+    let verdict = match pattern {
+        Array(pattern) => {
+            let Array(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+
+            match_types(&scrutinee.elem, &pattern.elem)?
+                && match_exprs(&scrutinee.len, &pattern.len)
+        }
+        Path(pattern) => {
+            if let Some(qself) = &pattern.qself {
+                return Err(unsupported_syntax_error(qself, "<T as Trait> syntax"));
+            }
+
+            let Path(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+
+            scrutinee.qself.is_none() && match_paths(&scrutinee.path, &pattern.path)?
+        }
+        Ptr(pattern) => {
+            let Ptr(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            scrutinee.const_token == pattern.const_token
+                && scrutinee.mutability == pattern.mutability
+                && match_types(&scrutinee.elem, &pattern.elem)?
+        }
+        Reference(pattern) => {
+            if let Some(lifetime) = &pattern.lifetime {
+                return Err(unsupported_syntax_error(
+                    lifetime,
+                    "Lifetimes are ignored during type pattern matching. \
+                    Explicit lifetime syntax",
+                ));
+            }
+
+            let Reference(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+
+            scrutinee.mutability == pattern.mutability
+                && match_types(&scrutinee.elem, &pattern.elem)?
+        }
+        Slice(pattern) => {
+            let Slice(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            match_types(&scrutinee.elem, &pattern.elem)?
+        }
+        Tuple(pattern) => {
+            let Tuple(scrutinee) = scrutinee else {
+                return Ok(false);
+            };
+            scrutinee
+                .elems
+                .iter()
+                .try_equals_with(&pattern.elems, match_types)?
+        }
+
+        Never(_) => matches!(scrutinee, Never(_)),
+
+        _ => return Err(unsupported_syntax_error(&pattern, "This syntax")),
+    };
+
+    Ok(verdict)
+}
+
+fn unsupported_syntax_error(spanned: &impl Spanned, syntax: &str) -> Error {
+    err!(
+        spanned,
+        "{syntax} is not supported in type patterns yet. If you have \
+        a use case for this, please open an issue at \
+        https://github.com/elastio/bon/issues."
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use syn::parse_quote as pq;
+
+    #[track_caller]
+    fn assert_match_types(scrutinee: syn::Type, pattern: syn::Type) {
+        // Make sure pure wildcard matches everything
+        assert!(scrutinee.matches(&pq!(_)).unwrap());
+
+        assert!(scrutinee.matches(&pattern).unwrap());
+    }
+
+    #[track_caller]
+    fn assert_not_match_types(scrutinee: syn::Type, pattern: syn::Type) {
+        assert!(!scrutinee.matches(&pattern).unwrap());
+    }
+
+    #[track_caller]
+    fn assert_unsupported(pattern: syn::Type) {
+        let scrutinee: syn::Type = syn::parse_quote!(());
+        let result = scrutinee.matches(&pattern);
+        let err = result.unwrap_err().to_string();
+        assert!(
+            err.contains("is not supported in type patterns yet"),
+            "Error: {err}"
+        );
+    }
+
+    #[test]
+    fn array() {
+        assert_match_types(pq!([u8; 4]), pq!([u8; 4]));
+        assert_match_types(pq!([u8; 4]), pq!([_; 4]));
+        assert_match_types(pq!([u8; 4]), pq!([_; _]));
+        assert_match_types(pq!([u8; 4]), pq!([u8; _]));
+
+        assert_not_match_types(pq!([u8; 4]), pq!([u8; 5]));
+        assert_not_match_types(pq!([u8; 4]), pq!([_; 5]));
+
+        assert_not_match_types(pq!([u8; 4]), pq!([u16; 4]));
+        assert_not_match_types(pq!([u8; 4]), pq!([u16; _]));
+
+        assert_not_match_types(pq!([u8; 4]), pq!([_]));
+        assert_not_match_types(pq!([u8; 4]), pq!([u8]));
+    }
+
+    #[test]
+    fn path() {
+        assert_match_types(pq!(bool), pq!(bool));
+        assert_match_types(pq!(foo::Bar), pq!(foo::Bar));
+        assert_match_types(pq!(crate::foo::Bar), pq!(crate::foo::Bar));
+        assert_match_types(pq!(super::foo::Bar), pq!(super::foo::Bar));
+
+        assert_not_match_types(pq!(::Bar), pq!(Bar));
+        assert_not_match_types(pq!(Bar), pq!(::Bar));
+        assert_not_match_types(pq!(super::foo::Bar), pq!(crate::foo::Bar));
+
+        assert_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar<_>));
+        assert_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar<u32>));
+        assert_match_types(pq!(foo::Bar<u32, bool>), pq!(foo::Bar<u32, _>));
+        assert_match_types(pq!(foo::Bar<u32, bool>), pq!(foo::Bar<_, bool>));
+        assert_match_types(pq!(foo::Bar<u32, bool>), pq!(foo::Bar<u32, bool>));
+        assert_match_types(pq!(foo::Bar<u32, bool>), pq!(foo::Bar<_, _>));
+
+        assert_not_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar<bool>));
+        assert_not_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar));
+        assert_not_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar<u32, _>));
+        assert_not_match_types(pq!(foo::Bar<u32>), pq!(foo::Bar<_, _>));
+        assert_not_match_types(pq!(foo::Foo<u32>), pq!(foo::Bar<u32>));
+    }
+
+    #[test]
+    fn ptr() {
+        assert_match_types(pq!(*const u8), pq!(*const u8));
+        assert_match_types(pq!(*const u8), pq!(*const _));
+        assert_match_types(pq!(*mut u8), pq!(*mut u8));
+        assert_match_types(pq!(*mut u8), pq!(*mut _));
+
+        assert_not_match_types(pq!(*const u8), pq!(*mut u8));
+        assert_not_match_types(pq!(*const u8), pq!(*mut _));
+        assert_not_match_types(pq!(*mut u8), pq!(*const u8));
+        assert_not_match_types(pq!(*mut u8), pq!(*const _));
+    }
+
+    #[test]
+    fn reference() {
+        assert_match_types(pq!(&u8), pq!(&u8));
+        assert_match_types(pq!(&u8), pq!(&_));
+        assert_match_types(pq!(&mut u8), pq!(&mut u8));
+        assert_match_types(pq!(&mut u8), pq!(&mut _));
+
+        assert_match_types(pq!(&'a u8), pq!(&_));
+        assert_match_types(pq!(&'_ u8), pq!(&_));
+        assert_match_types(pq!(&'static u8), pq!(&_));
+        assert_match_types(pq!(&'a mut u8), pq!(&mut _));
+        assert_match_types(pq!(&'_ mut u8), pq!(&mut _));
+        assert_match_types(pq!(&'static mut u8), pq!(&mut _));
+
+        assert_match_types(pq!(&'a u8), pq!(&u8));
+        assert_match_types(pq!(&'_ u8), pq!(&u8));
+        assert_match_types(pq!(&'static u8), pq!(&u8));
+        assert_match_types(pq!(&'a mut u8), pq!(&mut u8));
+        assert_match_types(pq!(&'_ mut u8), pq!(&mut u8));
+        assert_match_types(pq!(&'static mut u8), pq!(&mut u8));
+
+        assert_not_match_types(pq!(&u8), pq!(&mut u8));
+        assert_not_match_types(pq!(&u8), pq!(&mut _));
+        assert_not_match_types(pq!(&mut u8), pq!(&u8));
+        assert_not_match_types(pq!(&mut u8), pq!(&_));
+    }
+
+    #[test]
+    fn slice() {
+        assert_match_types(pq!([u8]), pq!([u8]));
+        assert_match_types(pq!([u8]), pq!([_]));
+        assert_match_types(pq!(&[u8]), pq!(&[u8]));
+        assert_match_types(pq!(&[u8]), pq!(&[_]));
+        assert_match_types(pq!(&[u8]), pq!(&_));
+
+        assert_not_match_types(pq!([u8]), pq!([u16]));
+        assert_not_match_types(pq!([u8]), pq!([u8; 4]));
+    }
+
+    #[test]
+    fn tuple() {
+        assert_match_types(pq!((u8, bool)), pq!((u8, bool)));
+        assert_match_types(pq!((u8, bool)), pq!((_, _)));
+        assert_match_types(pq!((u8, bool)), pq!((u8, _)));
+        assert_match_types(pq!((u8, bool)), pq!((_, bool)));
+
+        assert_match_types(pq!(()), pq!(()));
+        assert_match_types(pq!((u8,)), pq!((u8,)));
+        assert_match_types(pq!((u8,)), pq!((_,)));
+
+        assert_not_match_types(pq!((u8, bool)), pq!((bool, u8)));
+        assert_not_match_types(pq!((u8, bool)), pq!((u8, bool, u8)));
+
+        assert_not_match_types(pq!((u8,)), pq!(()));
+        assert_not_match_types(pq!(()), pq!((u8,)));
+    }
+
+    #[test]
+    fn never() {
+        assert_match_types(pq!(!), pq!(!));
+        assert_not_match_types(pq!(!), pq!(bool));
+    }
+
+    #[test]
+    fn unsupported() {
+        assert_unsupported(pq!(dyn Trait));
+        assert_unsupported(pq!(dyn FnOnce()));
+
+        assert_unsupported(pq!(impl Trait));
+        assert_unsupported(pq!(impl FnOnce()));
+
+        assert_unsupported(pq!(fn()));
+
+        assert_unsupported(pq!(&'a _));
+        assert_unsupported(pq!(&'_ _));
+        assert_unsupported(pq!(&'static _));
+
+        assert_unsupported(pq!(for<'a> Trait<'a>));
+        assert_unsupported(pq!(for<'a> fn()));
+
+        assert_unsupported(pq!(<T as Trait>::Foo));
+    }
+}
diff --git a/bon-macros/src/util/ty.rs b/bon-macros/src/util/ty/mod.rs
similarity index 78%
rename from bon-macros/src/util/ty.rs
rename to bon-macros/src/util/ty/mod.rs
index 24c4eea1..7e4a40f7 100644
--- a/bon-macros/src/util/ty.rs
+++ b/bon-macros/src/util/ty/mod.rs
@@ -1,3 +1,7 @@
+mod match_types;
+
+use crate::util::prelude::*;
+
 pub(crate) trait TypeExt {
     /// Try downcasting the type to [`syn::Type::Path`]
     fn as_path(&self) -> Option<&syn::TypePath>;
@@ -17,6 +21,14 @@ pub(crate) trait TypeExt {
 
     /// Recursively strips the [`syn::Type::Group`] and [`syn::Type::Paren`] wrappers
     fn peel(&self) -> &Self;
+
+    /// Returns `true` if the given type matches the pattern. The types match only if
+    /// their tokens are equal or if they differ in the places where the pattern has
+    /// a wildcard [`syn::Type::Infer`] e.g. `Vec<i32>` matches the pattern `Vec<_>`.
+    ///
+    /// Any wildcards in `Self` will not be specially handled. Only wildcards in `pattern`
+    /// have semantic meaning.
+    fn matches(&self, pattern: &syn::Type) -> Result<bool>;
 }
 
 impl TypeExt for syn::Type {
@@ -79,4 +91,8 @@ impl TypeExt for syn::Type {
             _ => self,
         }
     }
+
+    fn matches(&self, pattern: &syn::Type) -> Result<bool> {
+        match_types::match_types(self, pattern)
+    }
 }
diff --git a/bon/src/private/ide.rs b/bon/src/private/ide.rs
new file mode 100644
index 00000000..70e7b229
--- /dev/null
+++ b/bon/src/private/ide.rs
@@ -0,0 +1,65 @@
+#![allow(non_upper_case_globals, missing_debug_implementations)]
+
+/// Completions for the top-level `builder` attribute.
+pub mod builder_top_level {
+    use super::*;
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#start-fn>
+    pub const start_fn: Option<Identifier> = None;
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#start-fn>
+    pub mod start_fn {
+        use super::*;
+
+        /// See the docs at <https://elastio.github.io/bon/reference/builder#start-fn>
+        pub const name: Identifier = Identifier;
+
+        /// See the docs at <https://elastio.github.io/bon/reference/builder#start-fn>
+        pub const vis: VisibilityString = VisibilityString;
+    }
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#finish-fn>
+    pub const finish_fn: Option<Identifier> = None;
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#builder-type>
+    pub const builder_type: Option<Identifier> = None;
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#expose-positional-fn>
+    pub const expose_positional_fn: Option<Identifier> = None;
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#expose-positional-fn>
+    pub mod expose_positional_fn {
+        use super::*;
+
+        /// See the docs at <https://elastio.github.io/bon/reference/builder#expose-positional-fn>
+        pub const name: Identifier = Identifier;
+
+        /// See the docs at <https://elastio.github.io/bon/reference/builder#expose-positional-fn>
+        pub const vis: Option<VisibilityString> = None;
+    }
+
+    /// See the docs at <https://elastio.github.io/bon/reference/builder#on>
+    pub mod on {
+        use super::*;
+
+        /// See the docs at <https://elastio.github.io/bon/reference/builder#on>
+        pub const into: Flag = Flag;
+    }
+}
+
+/// Visibility inside of a string literal. Empty string means private visibility.
+///
+/// Examples:
+///
+/// - `""` - the symbol is private (accessible only within the same module)
+/// - `"pub"` - the symbol is accessible outside of the crate
+/// - `"pub(crate)"` - the symbol is accessible anywhere inside of the crate, but not outside of it
+///
+/// [Rust reference](https://doc.rust-lang.org/reference/visibility-and-privacy.html)
+pub struct VisibilityString;
+
+/// [Rust reference](https://doc.rust-lang.org/reference/identifiers.html)
+pub struct Identifier;
+
+/// The presence of this attribute enables the behavior. The attribute has no value.
+pub struct Flag;
diff --git a/bon/src/private.rs b/bon/src/private/mod.rs
similarity index 83%
rename from bon/src/private.rs
rename to bon/src/private/mod.rs
index cacb2c34..62432fb0 100644
--- a/bon/src/private.rs
+++ b/bon/src/private/mod.rs
@@ -1,3 +1,6 @@
+/// Used for providing better IDE hints (completions and syntax highlighting).
+pub mod ide;
+
 /// Used to implement the `alloc` feature.
 #[cfg(feature = "alloc")]
 pub extern crate alloc;
diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts
index 86300e90..fcf77be5 100644
--- a/website/.vitepress/config.mts
+++ b/website/.vitepress/config.mts
@@ -1,5 +1,6 @@
 import { defineConfig } from "vitepress";
 import { abbr } from "@mdit/plugin-abbr";
+import * as v1 from "../v1/config.mjs";
 
 const base = "/bon/";
 
@@ -55,7 +56,8 @@ export default defineConfig({
 
         // https://vitepress.dev/reference/default-theme-config
         nav: [
-            { text: "Docs", link: "/docs/guide/overview" },
+            { text: "Guide", link: "/guide/overview" },
+            { text: "Reference", link: "/reference/builder" },
             { text: "Changelog", link: "/changelog" },
             { text: "Blog", link: "/blog" },
         ],
@@ -65,100 +67,125 @@ export default defineConfig({
         ],
 
         sidebar: {
-            "/docs": [
+            ...v1.sidebars,
+            "/guide": [
                 {
                     text: "Guide",
                     items: [
                         {
                             text: "Overview",
-                            link: "/docs/guide/overview",
+                            link: "/guide/overview",
                         },
                         {
-                            text: "Optional members",
-                            link: "/docs/guide/optional-members",
+                            text: "Optional Members",
+                            link: "/guide/optional-members",
                         },
                         {
                             text: "Compatibility",
-                            link: "/docs/guide/compatibility",
-                        },
-                        {
-                            text: "Into conversions",
-                            link: "/docs/guide/into-conversions",
+                            link: "/guide/compatibility",
                         },
                         {
                             text: "Documenting",
-                            link: "/docs/guide/documenting",
+                            link: "/guide/documenting",
                         },
                         {
                             text: "Limitations",
-                            link: "/docs/guide/limitations",
+                            link: "/guide/limitations",
                         },
                         {
                             text: "Benchmarks",
-                            link: "/docs/guide/benchmarks",
+                            link: "/guide/benchmarks",
                         },
                         {
                             text: "Alternatives",
-                            link: "/docs/guide/alternatives",
+                            link: "/guide/alternatives",
                         },
                         {
                             text: "Troubleshooting",
-                            link: "/docs/guide/troubleshooting",
+                            link: "/guide/troubleshooting",
                         },
                     ],
                 },
                 {
-                    text: "Contributing",
-                    link: "/docs/contributing",
+                    text: "Patterns",
+                    items: [
+                        {
+                            text: "Conditional Building",
+                            link: "/guide/patterns/conditional-building",
+                        },
+                        {
+                            text: "Fallible Builders",
+                            link: "/guide/patterns/fallible-builders",
+                        },
+                        {
+                            text: "Into Conversions In-Depth",
+                            link: "/guide/patterns/into-conversions-in-depth",
+                        },
+                    ],
                 },
+                {
+                    text: "Internal",
+                    items: [
+                        {
+                            text: "Contributing",
+                            link: "/guide/internal/contributing",
+                        },
+                    ],
+                },
+            ],
+            "/reference": [
                 {
                     text: "Reference",
                     items: [
                         {
                             text: "#[builder]",
-                            link: "/docs/reference/builder",
+                            link: "/reference/builder",
                             items: [
                                 {
-                                    text: "Top-level attributes",
-                                    link: "/docs/reference/builder#top-level-attributes",
+                                    text: "Top-Level Attributes",
+                                    link: "/reference/builder#top-level-attributes",
                                     items: [
                                         {
                                             text: "builder_type",
-                                            link: "/docs/reference/builder#builder-type",
+                                            link: "/reference/builder#builder-type",
                                         },
                                         {
                                             text: "expose_positional_fn",
-                                            link: "/docs/reference/builder#expose-positional-fn",
+                                            link: "/reference/builder#expose-positional-fn",
                                         },
                                         {
                                             text: "finish_fn",
-                                            link: "/docs/reference/builder#finish-fn",
+                                            link: "/reference/builder#finish-fn",
                                         },
                                         {
                                             text: "start_fn",
-                                            link: "/docs/reference/builder#start-fn",
+                                            link: "/reference/builder#start-fn",
+                                        },
+                                        {
+                                            text: "on",
+                                            link: "/reference/builder#on",
                                         },
                                     ],
                                 },
                                 {
-                                    text: "Member-level attributes",
-                                    link: "/docs/reference/builder#member-level-attributes",
+                                    text: "Member-Level Attributes",
+                                    link: "/reference/builder#member-level-attributes",
                                     items: [
                                         {
                                             text: "default",
-                                            link: "/docs/reference/builder#default",
+                                            link: "/reference/builder#default",
                                         },
                                         {
                                             text: "into",
-                                            link: "/docs/reference/builder#into",
+                                            link: "/reference/builder#into",
                                         },
                                         {
                                             text: "name",
-                                            link: "/docs/reference/builder#name",
+                                            link: "/reference/builder#name",
                                         },
                                         {
                                             text: "skip",
-                                            link: "/docs/reference/builder#skip",
+                                            link: "/reference/builder#skip",
                                         },
                                     ],
                                 },
@@ -166,7 +193,7 @@ export default defineConfig({
                         },
                         {
                             text: "#[bon]",
-                            link: "/docs/reference/bon",
+                            link: "/reference/bon",
                         },
                         {
                             text: "Other items on docs.rs",
diff --git a/website/.vitepress/theme/components/PageFooter.vue b/website/.vitepress/theme/components/PageFooter.vue
index 0d895ff4..6eb1551a 100644
--- a/website/.vitepress/theme/components/PageFooter.vue
+++ b/website/.vitepress/theme/components/PageFooter.vue
@@ -4,7 +4,7 @@
 
 <script setup lang="ts">
 import { useData } from 'vitepress';
-import { VPTeamMembers } from 'vitepress/theme'
+import { VPTeamMembers } from 'vitepress/theme';
 
 const { frontmatter } = useData();
 
@@ -18,6 +18,7 @@ const allPeople = [
         desc: "Creator of bon",
         links: [
             { icon: 'github', link: 'https://github.com/Veetaha' },
+            { icon: 'x', link: 'https://x.com/Veetaha' },
         ]
     },
 ]
diff --git a/website/.vitepress/theme/components/PageHeader.vue b/website/.vitepress/theme/components/PageHeader.vue
index 2bd8d085..06b54380 100644
--- a/website/.vitepress/theme/components/PageHeader.vue
+++ b/website/.vitepress/theme/components/PageHeader.vue
@@ -1,3 +1,21 @@
+<script setup lang="ts">
+import { useData, useRouter, withBase } from 'vitepress';
+import formatDate from '../utils/formatDate';
+import { computed } from 'vue';
+import { latestVersion, parseRouteAsVersioned } from '../utils/versioning';
+
+const { frontmatter } = useData();
+
+const router = useRouter();
+
+const versionedRoute = computed(() => parseRouteAsVersioned(router.route.path));
+const isLatestVersion = computed(() =>
+    versionedRoute.value == null || versionedRoute.value.selectedVersion === latestVersion
+);
+
+
+</script>
+
 <template>
     <header class="vp-doc">
         <h1 v-if="frontmatter.title">
@@ -6,17 +24,20 @@
         <h3 v-if="frontmatter.date">
             {{ formatDate(frontmatter.date) }}
         </h3>
+        <div v-if="!isLatestVersion" class="warning custom-block">
+            <div class="custom-block-title">WARNING</div>
+            <p>
+                You are viewing the docs for an older major version
+                of <code>bon</code> ({{ versionedRoute.selectedVersion }}).
+            </p>
+            <p>
+                <a :href="withBase(`/${versionedRoute.sectionRoot}`)">Click here</a>
+                to view the docs for the latest version ({{ latestVersion }}).
+            </p>
+        </div>
     </header>
 </template>
 
-<script setup lang="ts">
-import { useData } from 'vitepress';
-import formatDate from '../utils/formatDate';
-
-const { frontmatter } = useData();
-
-</script>
-
 <style scoped>
 header {
     margin: 2rem 0;
diff --git a/website/.vitepress/theme/components/VersionSwitcher.vue b/website/.vitepress/theme/components/VersionSwitcher.vue
new file mode 100644
index 00000000..7adf3c89
--- /dev/null
+++ b/website/.vitepress/theme/components/VersionSwitcher.vue
@@ -0,0 +1,63 @@
+<script setup lang="ts">
+
+// This implementation was based on the component from `vitepress-versioning-plugin`:
+// https://github.com/IMB11/vitepress-versioning-plugin/blob/16bc30996b2245bfd48cb15732900ba214c3affb/src/components/VersionSwitcher.vue#L1
+
+import { useRouter } from 'vitepress';
+import { computed } from 'vue';
+import VPMenuLink from 'vitepress/dist/client/theme-default/components/VPMenuLink.vue';
+import VPFlyout from 'vitepress/dist/client/theme-default/components/VPFlyout.vue';
+import { parseRouteAsVersioned, latestVersion, versions } from '../utils/versioning';
+
+const router = useRouter();
+
+const latestVersionText = `${latestVersion} (latest)`;
+
+const versionedRoute = computed(() => parseRouteAsVersioned(router.route.path));
+const selectedVersion = computed(() => versionedRoute.value.selectedVersion);
+const sectionRoot = computed(() => versionedRoute.value.sectionRoot);
+
+const selectedVersionText = computed(() =>
+    selectedVersion.value === latestVersion
+        ? latestVersionText
+        : selectedVersion.value
+);
+
+</script>
+
+<template>
+    <VPFlyout v-if="versionedRoute" icon="vpi-versioning" :button="selectedVersionText" label="Switch Version">
+        <div class="items">
+            <template v-for="version in versions" :key="version">
+                <VPMenuLink v-if="selectedVersion != version" :item="{
+                    text: version === latestVersion ? latestVersionText : version,
+                    link: version === selectedVersion
+                        ? undefined
+                        : version === latestVersion
+                            ? `/${sectionRoot}`
+                            : `/${version}/${sectionRoot}`,
+                }" />
+            </template>
+        </div>
+    </VPFlyout>
+</template>
+
+<style>
+.vpi-versioning.option-icon {
+    margin-right: 2px !important;
+}
+
+.vpi-versioning {
+    --icon: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgMjQgMjQiIHN0cm9rZS13aWR0aD0iMi4yIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNvbG9yPSIjMDAwMDAwIj48cGF0aCBkPSJNMTcgN0MxOC4xMDQ2IDcgMTkgNi4xMDQ1NyAxOSA1QzE5IDMuODk1NDMgMTguMTA0NiAzIDE3IDNDMTUuODk1NCAzIDE1IDMuODk1NDMgMTUgNUMxNSA2LjEwNDU3IDE1Ljg5NTQgNyAxNyA3WiIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIuMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48L3BhdGg+PHBhdGggZD0iTTcgN0M4LjEwNDU3IDcgOSA2LjEwNDU3IDkgNUM5IDMuODk1NDMgOC4xMDQ1NyAzIDcgM0M1Ljg5NTQzIDMgNSAzLjg5NTQzIDUgNUM1IDYuMTA0NTcgNS44OTU0MyA3IDcgN1oiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyLjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PC9wYXRoPjxwYXRoIGQ9Ik03IDIxQzguMTA0NTcgMjEgOSAyMC4xMDQ2IDkgMTlDOSAxNy44OTU0IDguMTA0NTcgMTcgNyAxN0M1Ljg5NTQzIDE3IDUgMTcuODk1NCA1IDE5QzUgMjAuMTA0NiA1Ljg5NTQzIDIxIDcgMjFaIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNNyA3VjE3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD48cGF0aCBkPSJNMTcgN1Y4QzE3IDEwLjUgMTUgMTEgMTUgMTFMOSAxM0M5IDEzIDcgMTMuNSA3IDE2VjE3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi4yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvcGF0aD48L3N2Zz4=")
+}
+
+.VPFlyout .button {
+    padding: 0;
+}
+
+.VPFlyout .menu {
+    position: absolute;
+    right: unset;
+    z-index: 1;
+}
+</style>
diff --git a/website/.vitepress/theme/layouts/CustomLayout.vue b/website/.vitepress/theme/layouts/CustomLayout.vue
index 5032a318..e4ff8a0f 100644
--- a/website/.vitepress/theme/layouts/CustomLayout.vue
+++ b/website/.vitepress/theme/layouts/CustomLayout.vue
@@ -2,6 +2,7 @@
 import DefaultTheme from 'vitepress/theme'
 import PageHeader from '../components/PageHeader.vue';
 import PageFooter from '../components/PageFooter.vue';
+import VersionSwitcher from '../components/VersionSwitcher.vue';
 import mediumZoom from 'medium-zoom';
 import { useRoute } from 'vitepress';
 import { nextTick, onMounted, watch } from 'vue';
@@ -35,6 +36,9 @@ onMounted(initZoom);
         <template #doc-after>
             <PageFooter />
         </template>
+        <template #sidebar-nav-before>
+            <VersionSwitcher />
+        </template>
     </Layout>
 </template>
 
diff --git a/website/.vitepress/theme/utils/versioning.ts b/website/.vitepress/theme/utils/versioning.ts
new file mode 100644
index 00000000..db72722a
--- /dev/null
+++ b/website/.vitepress/theme/utils/versioning.ts
@@ -0,0 +1,41 @@
+import { useRouter, withBase } from "vitepress";
+
+export const latestVersion = "v2";
+export const versions = ["v2", "v1"];
+
+export const sectionRoots = {
+    guide: "guide/overview",
+    reference: "reference/builder",
+};
+
+const base = withBase("/");
+
+export function parseRouteAsVersioned(route: string) {
+    const pathComponents = route.slice(base.length).split("/");
+
+    const prefix = pathComponents[0];
+
+    if (prefix == null) {
+        return undefined;
+    }
+
+    let selectedVersion = latestVersion;
+
+    // Check if we are on a non-latest route (contains a vN prefix)
+    if (/^v\d+$/.test(prefix)) {
+        selectedVersion = prefix;
+        pathComponents.shift();
+    }
+
+    const sectionRoot = sectionRoots[pathComponents[0]];
+
+    if (sectionRoot == null) {
+        return undefined;
+    }
+
+    // Skip the version path of the path
+    return {
+        selectedVersion,
+        sectionRoot,
+    };
+}
diff --git a/website/LICENSE-APACHE b/website/LICENSE-APACHE
deleted file mode 120000
index 965b606f..00000000
--- a/website/LICENSE-APACHE
+++ /dev/null
@@ -1,1 +0,0 @@
-../LICENSE-APACHE
\ No newline at end of file
diff --git a/website/LICENSE-MIT b/website/LICENSE-MIT
deleted file mode 120000
index 76219eb7..00000000
--- a/website/LICENSE-MIT
+++ /dev/null
@@ -1,1 +0,0 @@
-../LICENSE-MIT
\ No newline at end of file
diff --git a/website/README.md b/website/README.md
index f14f721c..cdebba6b 100644
--- a/website/README.md
+++ b/website/README.md
@@ -10,7 +10,7 @@ If you want to make changes to the website, you need to be able to preview the u
 
 First, make sure you have [NodeJS installed](https://nodejs.org/en/download/package-manager).
 
-Then install the dependencies of the website with the following command. This command needs to be run inside the `website` directory.
+Then install the dependencies of the website with the following command. This command must be run inside of the `website` directory.
 
 ```bash
 npm install
@@ -23,3 +23,18 @@ npm run dev
 ```
 
 Consult [VitePress](https://vitepress.dev/) docs for help if you need any.
+
+#### License
+
+<sup>
+Licensed under either of <a href="https://github.com/elastio/bon/blob/master/LICENSE-APACHE">Apache License, Version
+2.0</a> or <a href="https://github.com/elastio/bon/blob/master/LICENSE-MIT">MIT license</a> at your option.
+</sup>
+
+<br>
+
+<sub>
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+</sub>
diff --git a/website/blog/how-to-do-named-function-arguments-in-rust.md b/website/blog/how-to-do-named-function-arguments-in-rust.md
index 4867147e..5996162d 100644
--- a/website/blog/how-to-do-named-function-arguments-in-rust.md
+++ b/website/blog/how-to-do-named-function-arguments-in-rust.md
@@ -115,7 +115,7 @@ We only placed `#[bon::builder]` on top of a regular Rust function. We didn't ne
 
 The builder generated by [`bon`] allows the caller to omit any parameters that have `Option` type. That part is covered.
 
-It supports almost any function syntax. It works fine with functions inside of `impl` blocks, `async` functions, generic parameters, lifetimes (including implicit and anonymous lifetimes), `impl Trait` syntax, etc. The full list of supported syntax is [here](../docs/guide/overview#supported-syntax-for-functions).
+It supports almost any function syntax. It works fine with functions inside of `impl` blocks, `async` functions, generic parameters, lifetimes (including implicit and anonymous lifetimes), `impl Trait` syntax, etc.
 
 It also never panics. All errors such as missing required arguments are compile-time errors ✔️.
 
@@ -131,6 +131,11 @@ use bon::builder;
 #[builder]
 struct User {
     id: u32,
+
+    // This attribute makes the setter accept `impl Into<String>`
+    // which let's us pass an `&str` directly and it'll be automatically
+    // converted into `String`.
+    #[builder(into)]
     name: String,
 }
 
@@ -140,12 +145,12 @@ User::builder()
     .build();
 ```
 
-So.. you can use just one `#[builder]` solution consistently for everything. Builders for functions and structs both share the same API design, which allows you, for example, to switch between a `#[builder]` on a struct and a `#[builder]` on a method that creates a struct. This won't be an API-breaking change for you consumers ([details](../docs/guide/compatibility#moving-builder-from-the-struct-to-the-new-method)).
+So.. you can use just one `#[builder]` solution consistently for everything. Builders for functions and structs both share the same API design, which allows you, for example, to switch between a `#[builder]` on a struct and a `#[builder]` on a method that creates a struct. This won't be an API-breaking change for you consumers ([details](../guide/compatibility#moving-builder-from-the-struct-to-the-new-method)).
 
 
 ## Summary
 
-If you'd like to know more about `bon` and use it in your code, then visit the [guide page with the full overview](../docs/guide/overview).
+If you'd like to know more about `bon` and use it in your code, then visit the [guide page with the full overview](../guide/overview).
 
 [`bon`] is fully open-source, and [available on crates.io](https://crates.io/crates/bon). Its code is public on [Github](https://github.com/elastio/bon). Consider giving it a star ⭐ if you liked the idea and the implementation.
 
@@ -161,4 +166,4 @@ You can leave comments for this post [on Reddit](https://www.reddit.com/r/rust/c
 :::
 
 
-[`bon`]: ../docs/guide/overview
+[`bon`]: ../guide/overview
diff --git a/website/blog/the-weird-of-function-local-types-in-rust.md b/website/blog/the-weird-of-function-local-types-in-rust.md
index cd9e7839..9da15bed 100644
--- a/website/blog/the-weird-of-function-local-types-in-rust.md
+++ b/website/blog/the-weird-of-function-local-types-in-rust.md
@@ -7,7 +7,7 @@ outline: deep
 
 ## Context
 
-I was writing documentation for my proc-macro crate that generates builders for functions and structs called [`bon`](../docs/guide/overview). I made the following simple example of code to compare [`bon`](../docs/guide/overview) with the other alternative crate [`buildstructor`](https://docs.rs/buildstructor/latest/buildstructor/).
+I was writing documentation for my proc-macro crate that generates builders for functions and structs called [`bon`](../guide/overview). I made the following simple example of code to compare [`bon`](../guide/overview) with the other alternative crate [`buildstructor`](https://docs.rs/buildstructor/latest/buildstructor/).
 
 ```rust compile_fail
 #[derive(buildstructor::Builder)]
@@ -218,7 +218,7 @@ Let's see what the generated code for this example may look like (very simplifie
 
 ::: tip
 
-The real code generated by [`#[bon::builder]`](../docs/guide/overview) is a bit more complex, it uses typestate pattern to catch all potential developer errors at compile time 🐱.
+The real code generated by [`#[bon::builder]`](../guide/overview) is a bit more complex, it uses typestate pattern to catch all potential developer errors at compile time 🐱.
 
 :::
 
@@ -323,7 +323,7 @@ The core problem is the conflict:
 - We want to make the builder's fields private, so we need to define the builder struct inside of a child module.
 - We want to reference types from the surrounding scope in the builder's fields, including local items, so we can't define the builder struct inside the child module.
 
-This is the problem that I found in `buildstructor`. The only way to solve this is to make a compromise, which I did when implementing [`#[bon::builder]`](../docs/guide/overview). The compromise is not to use a child module, and obfuscate the private fields of the builder struct with leading `__` and `#[doc(hidden)]` attributes to make it hard for the user to access them (even though not physically impossible).
+This is the problem that I found in `buildstructor`. The only way to solve this is to make a compromise, which I did when implementing [`#[bon::builder]`](../guide/overview). The compromise is not to use a child module, and obfuscate the private fields of the builder struct with leading `__` and `#[doc(hidden)]` attributes to make it hard for the user to access them (even though not physically impossible).
 
 But then... Defining types inside of functions is rather a niche use case. How do child modules in macro-generated code break the doc test mentioned at the beginning of this article?
 
diff --git a/website/changelog.md b/website/changelog.md
index ecfc6788..ec4a6bc9 100644
--- a/website/changelog.md
+++ b/website/changelog.md
@@ -71,4 +71,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
-- Initial release 🎉. See the [`bon` crate overview for details](https://elastio.github.io/bon/docs/guide/overview).
+- Initial release 🎉. See the [`bon` crate overview for details](https://elastio.github.io/bon/guide/overview).
diff --git a/website/guide/alternatives.md b/website/guide/alternatives.md
new file mode 100644
index 00000000..22038963
--- /dev/null
+++ b/website/guide/alternatives.md
@@ -0,0 +1,89 @@
+---
+aside: false
+---
+
+# Alternatives
+
+There are several other existing alternative crates that generate builders. `bon` was designed with many lessons learned from them. Here is a table that compares the builder crates with some additional explanations below.
+
+<!-- If you want to edit the table below make sure to reduce the font size in editor or turn off word wrap to easier view the table -->
+
+Feature                                                  | `bon`              | [`buildstructor`]  | [`typed-builder`]                                                   | [`derive_builder`]
+---------------------------------------------------------|--------------------|--------------------|---------------------------------------------------------------------|-------------------
+Builder for structs                                      | :white_check_mark: | :white_check_mark: | :white_check_mark:                                                  | :white_check_mark:
+Builder for free functions                               | :white_check_mark: |                    |                                                                     |
+Builder for associated methods                           | :white_check_mark: | :white_check_mark: |                                                                     |
+Panic safe                                               | :white_check_mark: | :white_check_mark: | :white_check_mark:                                                  | `build()` returns a `Result`
+Member of `Option` type is optional by default           | :white_check_mark: | :white_check_mark: | <span class="nobr">opt-in `#[builder(default)]`</span>              | <span class="nobr">opt-in `#[builder(default)]`</span>
+Making required member optional is compatible by default | :white_check_mark: | :white_check_mark: | <span class="nobr">opt-in `#[builder(setter(strip_option))]`</span> | <span class="nobr">opt-in `#[builder(setter(strip_option))]`</span>
+Generates `T::builder()` method                          | :white_check_mark: | :white_check_mark: | :white_check_mark:                                                  | only `Builder::default()`
+Automatic `Into` conversion in setters                   | :white_check_mark: | :white_check_mark: |                                                                     |
+ `impl Trait` supported for functions                    | :white_check_mark: |                    |                                                                     |
+Anonymous lifetimes supported for functions              | :white_check_mark: |                    |                                                                     |
+`Self` mentions in functions/structs are supported       | :white_check_mark: |                    |                                                                     |
+Positional function is hidden by default                 | :white_check_mark: |                    |                                                                     |
+Special setter methods for collections                   | [(see below)][r1]  | :white_check_mark: |                                                                     | :white_check_mark:
+Custom methods can be added to the builder type          |                    |                    | :white_check_mark: ([mutators])                                     | :white_check_mark:
+Builder may be configured to use &self/&mut self         |                    |                    |                                                                     | :white_check_mark:
+
+## Function builder fallback paradigm
+
+The builder crates `typed-builder` and `derive_builder` have a bunch of attributes that allow users to insert custom behavior into the building process of the struct. However, `bon` and `buildstructor` avoid the complexity of additional config attributes for advanced use cases by proposing the user to fallback to defining a custom function with the `#[builder]` attached to it where it's possible to do anything you want.
+
+However, `bon` still provides some simple attributes for common use cases to configure the behavior without falling back to a more verbose syntax.
+
+## Special setter methods for collections
+
+Other builder crates provide a way to generate methods to build collections one element at a time. For example, `buildstructor` even generates such methods by default:
+
+```rust
+#[derive(buildstructor::Builder)]
+struct User {
+    friends: Vec<String>
+}
+
+fn main() {
+    User::builder()
+        .friend("Foo")
+        .friend("Bar")
+        .friend("`String` value is also accepted".to_owned())
+        .build();
+}
+```
+
+::: tip
+
+Why is there an explicit `main()` function in this code snippet 🤔? It's a long story explained in a [blog post](../blog/the-weird-of-function-local-types-in-rust) (feel free to skip).
+
+:::
+
+This feature isn't available today in `bon`, but it's planned for the future. However, it won't be enabled by default, but rather be opt-in like it is in `derive_builder`.
+
+The problem of this feature is that a setter that pushes an element into a collection like that may confuse the reader in case if only one element is pushed. This may hide the fact that the member is actually a collection called `friends` in plural. However, this feature is still useful to provide backwards-compatibility when changing the type of a member from `T` or `Option<T>` to `Collection<T>`.
+
+Alternatively, `bon` provides a separate solution. `bon` exposes `bon::vec![]`, `bon::map!{}`, and `bon::set![]` macros that include automatic `Into` conversion for every item. So in `bon` syntax it would look like this:
+
+```rust
+use bon::builder;
+
+#[builder]
+struct User {
+    friends: Vec<String>
+}
+
+User::builder()
+    .friends(bon::vec![
+      "Foo",
+      "Bar",
+      "`String` value is also accepted".to_owned(),
+    ])
+    .build();
+```
+
+Another difference is that fields of collection types are considered required by default, which isn't the case in `buildstructor`.
+
+[`buildstructor`]: https://docs.rs/buildstructor/latest/buildstructor/
+[`typed-builder`]: https://docs.rs/typed-builder/latest/typed_builder/
+[`derive_builder`]: https://docs.rs/derive_builder/latest/derive_builder/
+[mutators]: https://docs.rs/typed-builder/latest/typed_builder/derive.TypedBuilder.html#mutators
+[r1]: #special-setter-methods-for-collections
diff --git a/website/docs/guide/benchmarks.md b/website/guide/benchmarks.md
similarity index 100%
rename from website/docs/guide/benchmarks.md
rename to website/guide/benchmarks.md
diff --git a/website/guide/compatibility.md b/website/guide/compatibility.md
new file mode 100644
index 00000000..5e89634a
--- /dev/null
+++ b/website/guide/compatibility.md
@@ -0,0 +1,206 @@
+# Compatibility
+
+## Making a required member optional
+
+It's totally backward compatible to make a required member optional by changing the type from `T` to `Option<T>` or by adding [`#[builder(default)]`](../reference/builder.md#default) to it.
+
+This is because both required and optional members have a setter that accepts `T` (not wrapped in an `Option`). The only change to the public API when making the required member optional is that a `maybe_`-prefixed setter is added to the builder. That new method accepts an `Option<T>`.
+
+**Example:**
+
+Suppose you have a function with a required argument, and there is existing code that sets that required argument using builder syntax.
+
+```rust
+use bon::builder;
+
+#[builder]
+fn get_page(password: &str) -> String {
+    format!("Secret knowledge")
+}
+
+// Existing code that uses the builder API
+let page = get_page()
+    .password("I know the password!")
+    .call();
+assert_eq!(page, "Secret knowledge");
+```
+
+Then you change this function to take an `Option<T>`. This is totally fine, the old code that sets that parameter to `T` still compiles:
+
+```rust
+use bon::builder;
+
+#[builder]
+fn get_page(password: Option<&str>) -> String { // [!code highlight]
+    format!("Secret knowledge")
+}
+
+// Existing code that uses the builder API (unchanged, still compiles)
+let page = get_page()
+    .password("I know the password!")
+    .call();
+assert_eq!(page, "Secret knowledge");
+
+// Now this code can also pass `Option<T>`, including `None` to the function
+// using the new `maybe_password` method.
+get_page()
+    .maybe_password(Some("password"))
+    .call();
+```
+
+## Switching between `Option<T>` and `#[builder(default)]`
+
+Switching between `Option<T>` for the member type and `#[builder(default)]` on `T` is fully compatible. Nothing changes in the builder API when this happens.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder]
+fn example(filter: Option<String>) {}
+
+example().maybe_filter(Some("filter".to_owned())).call();
+```
+
+This code can be changed to use `#[builder(default)]` and the call site will still compile:
+
+```rust ignore
+use bon::builder;
+
+#[builder]
+fn example(
+    #[builder(default)]    // [!code ++]
+    filter: Option<String> // [!code --]
+    filter: String         // [!code ++]
+) {}
+
+example.maybe_filter(Some("filter".to_owned())).call();
+```
+
+## Marking member as unused with a leading `_`
+
+You may add `_` prefix to the member name to mark it as unused for the time being. The builder API won't change if you do that. Leading underscores are stripped from the setter names automatically.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct]
+use bon::builder;
+
+#[builder]
+struct Example {
+    _name: String
+}
+
+Example::builder()
+    .name("The setter is still called `name`".to_owned())
+    .build();
+```
+
+```rust [Free function]
+use bon::builder;
+
+#[builder]
+fn example(
+    _name: String
+) {}
+
+example()
+    .name("The setter is still called `name`".to_owned())
+    .call();
+```
+
+```rust [Associated method]
+use bon::{bon, builder};
+
+struct Example;
+
+#[bon]
+impl Example {
+    #[builder]
+    fn example(_name: String) {}
+}
+
+Example::example()
+    .name("The setter is still called `name`".to_owned())
+    .call();
+```
+
+:::
+
+## Moving `#[builder]` from the struct to the `new()` method
+
+`#[builder]` on a struct generates builder API that is fully compatible with placing `#[builder]` on the `new()` method with the signature similar to struct's fields.
+
+This means, for example, it's preferable to place the `#[builder]` attribute on top of your struct in most cases because it's convenient. However, if you need to have some custom logic during the construction of your type, you may simply create a `new()` method annotated with `#[builder]` where you can do anything you want to create an instance of your type.
+
+To keep your struct's public API compatible with the time when `#[builder]` was on the struct directly, the `new()` method must accept the same parameters as there were fields in the struct.
+
+**Example:**
+
+```rust ignore
+use bon::bon;
+
+// Previously we used `#[builder]` on the struct
+#[builder] // [!code --]
+struct User {
+    // But then we decided to change the internal representation
+    // of the `id` field to use `String` instead of `u32`
+    id: u32,                                                     // [!code --]
+    id: String,                                                  // [!code ++]
+    name: String,
+}
+
+// To preserve compatibility we need to define a `new()` method with `#[builder]`
+// that still accepts `u32` for `id` member.
+#[bon]                                      // [!code ++]
+impl User {                                 // [!code ++]
+    #[builder]                              // [!code ++]
+    fn new(id: u32, name: String) -> Self { // [!code ++]
+        Self {                              // [!code ++]
+            id: format!("u-{id}"),          // [!code ++]
+            name,                           // [!code ++]
+        }                                   // [!code ++]
+    }                                       // [!code ++]
+}                                           // [!code ++]
+
+// The caller's code didn't change. It still uses `u32` for `id` member.
+let user = User::builder()
+    // `id` is still accepted as a `u32` here
+    .id(1)
+    .name("Bon".to_owned())
+    .build();
+```
+
+## Adding #[builder] to existing code
+
+If your existing code defines functions with positional parameters in its public API that you'd like to change to use builder syntax, but you want to keep the old code compatible with the positional functions API, then you may use `#[builder(expose_positional_fn)]` attribute to keep both syntaxes available.
+
+See [this attribute's docs](../reference/builder#expose-positional-fn) for details.
+
+**Example**:
+
+```rust ignore
+use bon::builder;
+
+// The previous name of the positional function needs to be specified
+// as the value for `expose_positional_fn`.
+#[builder(expose_positional_fn = example)] // [!code ++]
+fn example(x: u32, y: u32) {}              // [!code --]
+fn example_builder(x: u32, y: u32) {}      // [!code ++]
+
+// The positional function is now available under the specified (old) name
+example(1, 2);
+
+// The builder syntax is also available under the new function name
+example_builder()
+    .x(1)
+    .y(2)
+    .call();
+```
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/docs/guide/documenting.md b/website/guide/documenting.md
similarity index 100%
rename from website/docs/guide/documenting.md
rename to website/guide/documenting.md
diff --git a/website/docs/contributing.md b/website/guide/internal/contributing.md
similarity index 100%
rename from website/docs/contributing.md
rename to website/guide/internal/contributing.md
diff --git a/website/guide/limitations.md b/website/guide/limitations.md
new file mode 100644
index 00000000..5362668e
--- /dev/null
+++ b/website/guide/limitations.md
@@ -0,0 +1,85 @@
+# Limitations
+
+Every tool has its constraints, and `bon` is not an exception. The limitations described below shouldn't generally occur in your day-to-day code, and if they do, there are ways to work around them. If you feel that some of the limitations are unacceptable, feel free to [open an issue] to ask for relaxing some of them.
+
+## Intra-doc links to `Self` on setter methods
+
+Documentation placed on the original function arguments or struct fields is copied verbatim to the documentation on the generated setter methods of the builder struct. The shortcoming of this approach is that references to `Self` break when moved into the `impl` block of the generated builder struct.
+
+`bon` checks for the presence of ``[`Self`]`` and `[Self]` in the documentation on the function arguments and struct fields. If there are any, then a compile error suggesting to use the full type name will be generated.
+
+The following example doesn't compile with the reference to ``[`Self`]``. The fix is to replace that reference with the actual name of the struct ``[`Foo`]``.
+
+```rust compile_fail
+use bon::bon;
+
+struct Foo;
+
+#[bon]
+impl Foo {
+    #[builder]
+    fn promote(
+        /// Promotes [`Self`] to the level specified in this argument // [!code --]
+        /// Promotes [`Foo`] to the level specified in this argument  // [!code ++]
+        new_level: String
+    ) {}
+}
+```
+
+## Implicit generic lifetimes
+
+Rust allows omitting generic lifetime parameters of a type in function parameters.
+
+**Example:**
+
+```rust compile_fail
+use bon::builder;
+
+struct User<'a> {
+    name: &'a str
+}
+
+#[builder]
+fn example(value: User) {} // [!code error]
+```
+
+In this example, the type `User` is referenced in the function argument without lifetime parameters. This breaks the logic of the `#[builder]` macro. It must know **all** the lifetime parameters involved in the function signature to properly generate lifetime parameters for the builder struct.
+
+Unfortunately, macros in Rust don't have access to semantic information. All that macros see is just a sequence of syntax tokens. All that the `#[builder]` macro sees in the example above is just this:
+
+```rust ignore
+fn example(value: User) {}
+```
+
+This means `#[builder]` macro thinks as if there are no lifetime parameters in the `User` type, and thus it generates the code that doesn't compile.
+
+To fix this, we need to make it clear to the `#[builder]` macro that `User` expects some lifetime parameters. This can be done like this:
+
+```rust compile_fail
+#[builder]
+fn example(value: User) {}     // [!code --]
+fn example(value: User<'_>) {} // [!code ++]
+```
+
+If you want to make sure your code doesn't accidentally omit a generic lifetime parameter you may enable the `rustc` lint called [`elided_lifetimes_in_paths`](https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html). This lint is `allow` by default, you can enable it in your `Cargo.toml` like this:
+
+```toml
+[package.lints.rust]
+elided_lifetimes_in_paths = "warn"
+```
+
+## `const` functions
+
+It's possible to place `#[builder]` on top of a `const fn`, but the generated builder methods won't be marked `const`. They use the non-const method `Into::into` to transition between type states. Except for that, the generated code should be `const`-compatible.
+
+If you have a strong use case that requires full support for `const`, feel free to [open an issue]. We'll figure something out for sure 🐱.
+
+## Conditional compilation
+
+Conditionally-compiled members aren't supported yet. The blocker for this feature is a lack of support for attributes in `where` bounds in the language. See [rust-lang/rust/#115590](https://github.com/rust-lang/rust/issues/115590) for details.
+
+[open an issue]: https://github.com/elastio/bon/issues
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/guide/optional-members.md b/website/guide/optional-members.md
new file mode 100644
index 00000000..f60a02e5
--- /dev/null
+++ b/website/guide/optional-members.md
@@ -0,0 +1,111 @@
+# Optional Members
+
+## `Option<T>`
+
+Setters generated for members of `Option<T>` type are optional to call. If they aren't invoked, then `None` will be used as the default.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder]
+fn example(level: Option<u32>) {}
+
+// We can call it without specifying the `level`
+example().call();
+```
+
+The `#[builder]` attribute generates two setters for each optional member. One setter accepts `T` and the other accepts `Option<T>`. The following setters will be generated in the example above (simplified):
+
+```rust ignore
+impl ExampleBuilder {
+    // Accepts the underlying value. Wraps it in `Some()` internally
+    fn level(value: u32) -> NextBuilderState { /* */ }
+
+    // Accepts the `Option` directly.
+    fn maybe_level(value: Option<u32>) -> NextBuilderState { /* */ }
+}
+```
+
+::: tip
+
+Thanks to this design, changing the member from required to optional [preserves compatibility](./compatibility#making-a-required-member-optional).
+
+:::
+
+---
+
+If you need to pass a simple literal value, then the syntax is very short
+
+```rust ignore
+example().level(42).call();
+```
+
+If you already have an `Option` variable somewhere or you need to dynamically decide if the value should be `Some` or `None`, then you can use the `maybe_` variant of the setter.
+
+```rust ignore
+let value = if some_condition {
+    Some(42)
+} else {
+    None
+};
+
+example().maybe_level(value).call();
+```
+
+## `#[builder(default)]`
+
+To make a member of non-`Option` type optional you may use the attribute [`#[builder(default)]`](../reference/builder#default). This attribute uses the [`Default`](https://doc.rust-lang.org/stable/std/default/trait.Default.html) trait or the provided expression to assign the default value for the member.
+
+::: tip
+
+Switching between `#[builder(default)]` and `Option<T>` is [compatible](./compatibility#switching-between-optiont-and-builderdefault).
+
+:::
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder]
+fn example(
+    // This uses the `Default` trait // [!code highlight]
+    #[builder(default)]              // [!code highlight]
+    a: u32,
+
+    // This uses the given custom default value // [!code highlight]
+    #[builder(default = 4)]                     // [!code highlight]
+    b: u32,
+) -> u32 {
+    a + b
+}
+
+// Here, the default values will be used `a = 0` and `b = 4` // [!code highlight]
+let result = example().call();
+assert_eq!(result, 4);
+
+// The same couple of setters `{member}(T)` and `maybe_{member}(Option<T>)` // [!code highlight]
+// are generated just like it works with members of `Option<T>` type        // [!code highlight]
+let result = example()
+    .a(3)
+    .b(5)
+    .call();
+assert_eq!(result, 8);
+
+let result = example()
+    .maybe_a(Some(3))
+    .maybe_b(Some(5))
+    .call();
+assert_eq!(result, 8);
+```
+
+## Conditional building
+
+Now that you know how optional members work you can check out the ["Conditional building" patterns](./patterns/conditional-building) or continue studying other features of `bon` by following the "Next page" link at the bottom.
+
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/guide/overview.md b/website/guide/overview.md
new file mode 100644
index 00000000..347deea5
--- /dev/null
+++ b/website/guide/overview.md
@@ -0,0 +1,274 @@
+<script setup>
+import { data as version } from '/data/version.data'
+const [_, versionWildcard] = version.match(/(\d+.\d+).\d+/);
+</script>
+
+# Overview
+
+`bon` is a Rust crate for generating compile-time-checked builders for functions and structs.
+
+If you are new to the concept of builders or named function arguments and you don't know what problems they may solve for you, then check out the motivational [blog post](../blog/how-to-do-named-function-arguments-in-rust).
+
+## Installation
+
+Add this to your `Cargo.toml` to use this crate:
+
+```toml-vue
+[dependencies]
+bon = "{{ versionWildcard }}"
+```
+
+You can opt out of `std` and `alloc` cargo features with `default-features = false` for `no_std` environments.
+
+
+## Builder for a function
+
+`bon` can turn a function with positional parameters into a function with "named" parameters via a builder. It's as easy as placing the `#[builder]` macro on top of it.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder] // [!code highlight]
+fn greet(name: &str, age: u32) -> String {
+    format!("Hello {name} with age {age}!")
+}
+
+let greeting = greet()
+    .name("Bon")
+    .age(24)
+    .call();
+
+assert_eq!(greeting, "Hello Bon with age 24!");
+```
+
+::: tip
+
+Many things are customizable with additional attributes. [`#[builder]` macro reference](../reference/builder) describes all of them.
+
+:::
+
+Any syntax for functions is supported including `async`, fallible, generic functions, `impl Trait`, etc. If you find an edge case where `bon` doesn't work, please [create an issue on GitHub](https://github.com/elastio/bon/issues).
+
+## Builder for an associated method
+
+You can also generate a builder for associated methods. For this to work you need to add a `#[bon]` macro on top of the `impl` block additionally.
+
+**Example:**
+
+```rust
+use bon::bon;
+
+struct Counter {
+    val: u32,
+}
+
+#[bon] // <- this macro is required on the impl block // [!code highlight]
+impl Counter {
+    #[builder] // [!code highlight]
+    fn new(initial: Option<u32>) -> Self {
+        Self {
+            val: initial.unwrap_or_default(),
+        }
+    }
+
+    #[builder] // [!code highlight]
+    fn increment(&mut self, diff: u32) {
+        self.val += diff;
+    }
+}
+
+let mut counter = Counter::builder()
+    .initial(3)
+    .build();
+
+counter
+    .increment()
+    .diff(3)
+    .call();
+
+assert_eq!(counter.val, 6);
+```
+
+::: details Why is that `#[bon]` macro on top of the `impl` block required? 🤔 (feel free to skip)
+
+There are a couple of technical reasons.
+
+First of all, it's the lack of surrounding context given to a proc macro in Rust. A proc macro sees only the syntax it is placed on top of. For example, the `#[builder]` macro inside of the `impl` block can't see the `impl Counter` part of the impl block above it. However, it needs that information to tell the actual type of `Self`.
+
+Second, the `#[builder]` proc macro generates new items such as the builder struct type definition, which it needs to output **adjacently** to the `impl` block itself. However, proc macros in Rust can only modify the part of the syntax they are placed on and generate new items on the same level of nesting. The `#[builder]` macro inside of the `impl` block can't just break out of it.
+
+:::
+
+::: details Why does it compile without an import of `bon::builder`? 🤔 (feel free to skip)
+
+This is because there is no separate `#[builder]` proc macro running in this case. Only the `#[bon]` macro handles code generation, it's an active attribute, while `#[builder]` is a dumb inert data attribute (see [the Rust Reference](https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes) for details about active and inert attributes).
+
+It wouldn't harm if `bon::builder` was imported. It won't shadow the inert `#[builder]` attribute, but the import of that macro will be reported as unused by the compiler.
+
+:::
+
+
+To follow the usual Rust builder naming conventions `bon` treats the method named `new` inside of the impl block specially. It generates functions with a bit different names.
+
+If `#[builder]` is placed on the method called `new`, then the generated functions are called:
+
+| Start function            | Finish function
+|---------------------------|---------------------
+| `builder() -> {T}Builder` | `build(self) -> T`
+
+For any other methods not called `new` and for any free function the naming is a bit different:
+
+| Start function                                | Finish function
+|-----------------------------------------------|---------------------
+| `{fn_name}() -> {T?}{PascalCaseFnName}Builder` | `call(self) -> T`
+
+## Builder for a struct
+
+`bon` supports the classic pattern of annotating a struct to generate a builder.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder]
+struct User {
+    id: u32,
+    name: String,
+}
+
+let user = User::builder()
+    .id(1)
+    .name("Bon".to_owned())
+    .build();
+
+assert_eq!(user.id, 1);
+assert_eq!(user.name, "Bon");
+```
+
+::: tip
+
+`#[builder]` on a struct generates builder API that is fully compatible with placing `#[builder]` on the `new()` method with a signature similar to the struct's fields.
+
+See [compatibility](./compatibility#moving-builder-from-the-struct-to-the-new-method) page for details.
+
+:::
+
+## No panics possible
+
+The builders generated by `#[builder]` use the typestate pattern to make sure all required parameters are filled and the same setters aren't called repeatedly to prevent unintentional overwrites and typos. If something is wrong, a compile error will be created. There are no potential panics and `unwrap()` calls inside of the builder.
+
+## `Option<T>` values are optional
+
+If your function argument or struct field (or member for short) is of type `Option<T>`, then the generated builder will not enforce setting a value for this member, defaulting to `None`.
+
+It also generates two setters: one accepts `T` and the other accepts `Option<T>`. The first avoids wrapping values with `Some()` on the call site. The second allows passing the `Option<T>` value directly.
+
+```rust
+use bon::builder;
+
+#[builder]
+struct Projection {
+    x: Option<u32>,
+    y: Option<u32>,
+
+    // Use an annotation for members of non-`Option` type
+    #[builder(default)]
+    z: u32,
+}
+
+// Both `x` and `y` will be set to `None`, `z` will be set to `0`
+Projection::builder().build();
+
+Projection::builder()
+    // Pass the value without wrapping it with `Some()`
+    .x(10)
+    // Or use a `maybe_`-prefixed setter that accepts `Option`
+    .maybe_y(Some(20))
+    // The APIs generated for `#[builder(default)]` and `Option<T>` are equivalent.
+    // `z` will be set to `0` when `build()` is called.
+    .maybe_z(None)
+    .build();
+```
+
+See [optional members](./optional-members) page for details.
+
+## `Into` conversions
+
+If you have members of type `String`, or `PathBuf`, and you need to set them to a hardcoded string literal, then you have to write `.to_owned()` or `.to_string()` or `.into()`.
+
+**Example:**
+
+```rust
+use bon::builder;
+use std::path::PathBuf;
+
+#[builder]               // [!code focus]
+struct Project {         // [!code focus]
+    name: String,        // [!code focus]
+    description: String, // [!code focus]
+    path: PathBuf,       // [!code focus]
+}                        // [!code focus]
+
+Project::builder()
+    .name("Bon".to_owned())                      // [!code focus]
+    .description("Awesome crate 🐱".to_string()) // [!code focus]
+    .path("/path/to/bon".into())                 // [!code focus]
+    .build();
+```
+
+However, you can ask `bon` to generate setters that accept `impl Into<T>` to remove the need for manual conversion.
+
+This can be configured with [`#[builder(into)]`](../reference//builder#into) for a single member or with [`#[builder(on({type}, into))]`](../reference/builder#on) for a many members at once.
+
+```rust
+use bon::builder;
+use std::path::PathBuf;
+
+// All setters for members of type `String` will accept `impl Into<String>` // [!code highlight]
+#[builder(on(String, into))]                                                // [!code highlight]
+struct Project {
+    name: String,
+    description: String,
+
+    // The setter for this member only will accept `impl Into<PathBuf>`    // [!code highlight]
+    #[builder(into)]                                                       // [!code highlight]
+    path: PathBuf,
+}
+
+Project::builder()
+    // &str is converted to `String` internally    // [!code highlight]
+    .name("Bon")
+    .description("Awesome crate 🐱")
+    // `&str` is converted to `PathBuf` internally // [!code highlight]
+    .path("/path/to/your/heart")
+    .build();
+```
+
+See the ["Into Conversions In-Depth"](./patterns/into-conversions-in-depth) page for more details and important caveats (!).
+
+## What's next?
+
+::: tip
+
+If you like the idea of this crate and want to say "thank you" or "keep up doing this" consider giving us a [star ⭐ on Github](https://github.com/elastio/bon). Any support and contribution are appreciated 🐱!
+
+:::
+
+This is just part of what's available in `bon`. You may consider reading the rest of the `Guide` section to harness the full power of `bon` and understand the decisions it makes. Just click on the "Next page" link at the bottom.
+
+However, feel free to skip the docs and just use the `#[builder]` macro in your code. It's designed to be intuitive, so it'll probably do the thing you want it to do already.
+
+If you can't figure something out, consult the docs and maybe use that search `🔍 Search` thing at the top to navigate. You may also create an issue or a discussion in the [Github repository](https://github.com/elastio/bon) for help.
+
+## Acknowledgments
+
+This project was heavily inspired by such awesome crates as [`buildstructor`](https://docs.rs/buildstructor), [`typed-builder`](https://docs.rs/typed-builder) and [`derive_builder`](https://docs.rs/derive_builder). This crate was designed with many lessons learned from them.
+
+See [alternatives](./alternatives) for comparison.
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/guide/patterns/conditional-building.md b/website/guide/patterns/conditional-building.md
new file mode 100644
index 00000000..a3430b34
--- /dev/null
+++ b/website/guide/patterns/conditional-building.md
@@ -0,0 +1,142 @@
+# Conditional Building
+
+On this page, we'll review a case when you have multiple branches in your code that need to set different values for different builder members. Since the builders generated by `bon` use the type-state pattern and the setters consume `self`, it is a bit more complicated for conditional code to use them. But, not until you know the patterns described below 🐱. So let's learn how to write better conditional code 📚.
+
+The patterns described here aren't mutually exclusive. You can combine them as you see necessary to keep your code clean.
+
+## Shared partial builder
+
+If your conditional code needs to set the same values for the same members, consider extracting this shared code into a variable that holds a partial builder.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder(on(String, into))]
+struct User {
+    name: String,
+    tags: Vec<String>,
+    alias: Option<String>,
+    description: Option<String>,
+}
+
+// Common part is here // [!code highlight]
+let user = User::builder()
+    .name("Bon")
+    .tags(vec!["confectioner".to_owned()]);
+
+// Differences are in each branch // [!code highlight]
+let user = if 2 + 2 == 4 {
+    user
+        .alias("Good girl")
+        .description("Knows mathematics 🐱")
+        .build()
+} else {
+    user
+        .description("Skipped math classes 😿")
+        .build()
+};
+```
+
+It's important to call the `build()` method inside each of the branches of conditional code. This way every branch returns the value of the same type.
+
+This also works with `match` expressions. Just remember to call `.build()` in every arm.
+
+::: tip Deep Thought 👀
+
+This pattern of branching and converging on the same code path can be described using the terms "upcasting" or "context loss".
+
+The part of the code inside of the conditional branches has more context than the code that comes after. The code inside of the conditional block has a strongly typed builder that encodes the info that, for example, the `alias` member was set (and thus we can no longer call this setter).
+
+Once the `build()` method is called, we no longer have the context of how exactly the `User` was built.
+
+:::
+
+## Shared total builder
+
+In contrast to the [shared partial builder](#shared-partial-builder), here we are going to use the builder strictly _after_ the conditional code. The conditional code needs to create the variables that hold the component values for the builder beforehand.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder(on(String, into))]
+struct User {
+    name: String,
+    tags: Vec<String>,
+    alias: Option<String>,
+    description: Option<String>,
+}
+
+let knows_math = 2 + 2 == 4;
+
+// Differences are in branches for each variable // [!code highlight]
+let alias = if knows_math {
+    Some("Good girl")
+} else {
+    None
+};
+// ^^^^ This can be reduced to a single line with `bool::then_some()`
+// (try it yourself)
+
+let description = if knows_math {
+    "Knows mathematics 🐱"
+} else {
+    "Skipped math classes 😿"
+};
+
+// Common part is here // [!code highlight]
+let user = User::builder()
+    .name("Bon")
+    .tags(vec!["confectioner".to_owned()])
+    // Use the `maybe_` setter to provide an `Option<T>`
+    .maybe_alias(alias)
+    // If the description could be `None`, we'd use `maybe_description`,
+    // but in this case, all branches set the `description`, so we can
+    // use this shorter setter that wraps with `Some()` internally
+    .description(description)
+    .build();
+```
+
+In this case, we create a variable for each conditional member beforehand and initialize them separately, then we just pass the results to the builder. We benefit from the `maybe_` setters for [optional members](../optional-members) such that we can pass the `Option` values directly.
+
+::: tip NOTE
+
+However, creating separate variables is not strictly required. You can inline the usage of the variables in such a simple code like here, where each branch of the `if` takes a single line. Anyhow, branches can be much bigger in real code.
+
+:::
+
+If you'd like to avoid checking the same condition multiple times, you can write a single `if` that returns a tuple of values for several members:
+
+```rust
+let (alias, description) = if 2 + 2 == 4 {
+    (Some("Good girl"), "Knows mathematics 🐱")
+} else {
+    (None, "Skipped math classes 😿")
+};
+```
+
+However, when the number of members in the conditional logic grows, such an `if` with tuples becomes hard to read. In such a case, consider defining variables outside of the `if` or using the [shared partial builder](#shared-partial-builder) pattern instead.
+
+**Example:**
+
+```rust
+let mut alias = None;
+let description;
+
+if 2 + 2 == 4 {
+    alias = Some("Good girl");
+    description = "Knows mathematics 🐱";
+} else {
+    description = "Skipped math classes 😿";
+}
+
+// Pass the variables to the builder now
+// ...
+```
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/guide/patterns/fallible-builders.md b/website/guide/patterns/fallible-builders.md
new file mode 100644
index 00000000..20f6ebb8
--- /dev/null
+++ b/website/guide/patterns/fallible-builders.md
@@ -0,0 +1,41 @@
+# Fallible Builders
+
+With `bon`, you can write a builder that validates its inputs and returns a `Result`. It's possible to do this only via the function or associated method syntax.
+
+If you need to build a struct and do some validation, you won't be able to use the `#[builder]` annotation on the struct for that. You'll *have to* define your logic in the associated constructor method (e.g. `new`).
+
+**Example:**
+
+```rust
+use anyhow::Error;
+use bon::bon;
+
+struct User {
+    id: u32,
+    name: String,
+}
+
+#[bon]
+impl User {
+    #[builder]
+    fn new(id: u32, name: String) -> Result<Self, Error> {
+        if name.is_empty() {
+            return Err(anyhow::anyhow!("Empty name is disallowed (user id: {id})"));
+        }
+
+        Ok(Self { id, name })
+    }
+}
+
+// The `build()` method returns a `Result`
+let result = User::builder()
+    .id(42)
+    .name(String::new())
+    .build();
+
+if let Err(error) = result {
+    // Handle the error
+}
+```
+
+If you have a use case for some convenience attributes to do automatic validations using the `#[builder]` macro with the struct syntax, then add a 👍 reaction to [this Github issue](https://github.com/elastio/bon/issues/34).
diff --git a/website/guide/patterns/into-conversions-in-depth.md b/website/guide/patterns/into-conversions-in-depth.md
new file mode 100644
index 00000000..f5bcabb8
--- /dev/null
+++ b/website/guide/patterns/into-conversions-in-depth.md
@@ -0,0 +1,312 @@
+---
+outline: deep
+---
+
+
+# `Into` Conversions In-Depth
+
+## Preface
+
+This is the continuation of the ["Into conversions" section](../overview#into-conversions) from the general overview page. This page describes the important caveats of using `impl Into` that you should know before enabling them.
+
+Make sure you are familiar with the standard [`From`](https://doc.rust-lang.org/stable/std/convert/trait.From.html) and [`Into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html) traits before you proceed. Reading their docs is pretty much enough. For example, you should know that every type that implements `From<T>` automatically implements `Into<T>`. Also, you should know that you can pass a value of type `T` at no cost directly to a function that accepts `impl Into<T>` thanks to [this blanket](https://github.com/rust-lang/rust/blob/1a94d839be8b248b972b9e022cb940d56de72fa1/library/core/src/convert/mod.rs#L763-L771) impl in `std`.
+
+::: warning
+
+This is generally a controversial topic 🐱. Some people like to be more explicit, but others prefer the shorter notation. This also depends on the kind of code you are writing.
+
+If you prefer being explicit in code, feel free not to use `Into` conversions at all. They are fully opt-in. This article isn't prescriptive. The syntax savings are arguably small, so use your best judgement, and refer to this page if you can't decide.
+
+:::
+
+We'll cover the following:
+
+- [Use `Into` conversions](#reasons-to-use-into-conversions)
+- [Avoid `Into` conversions](#reasons-to-avoid-into-conversions)
+
+## Use `Into` conversions
+
+The main advantage of `impl Into` in setters is that it reduces the boilerplate for the caller. The code becomes shorter and cleaner, although not without [the drawbacks](#reasons-to-avoid-into-conversions).
+
+`Into` conversions usually make sense only if *all of the following* are true (AND):
+
+::: tip The Rules of `Into`
+
+1. The code where the builder is supposed to be *used* is not performance-sensitive.
+2. The builder is going to be used with literal values a lot or require wrapping the values.
+
+:::
+
+
+### Shorter syntax for literals
+
+Here is an example that shows the *non-exhaustive* list of standard types where it's usually fine to enable `Into` conversions.
+
+::: tip
+
+Switch between the UI tabs in the code snippets below to see how the code looks like with `Into Conversions` enabled and with the `Default` syntax.
+
+:::
+
+::: code-group
+```rust [Into Conversions]
+use bon::builder;
+
+#[builder]
+struct Example {
+    #[builder(into)] // [!code highlight]
+    string: String,
+
+    #[builder(into)]                                                            // [!code highlight]
+    path_buf: std::path::PathBuf,
+
+    #[builder(into)] // [!code highlight]
+    ip_addr: std::net::IpAddr,
+}
+
+Example::builder()
+    // We can pass `&str` literal                                        // [!code highlight]
+    .string("string literal")                                            // [!code highlight]
+    // We can pass `&str` literal or a String                            // [!code highlight]
+    .path_buf("string/literal")                                          // [!code highlight]
+    // We can pass an array of IP components or `Ipv4Addr` or `Ipv6Addr` // [!code highlight]
+    .ip_addr([127, 0, 0, 1])                                             // [!code highlight]
+    .build();
+```
+
+```rust [Default]
+use bon::builder;
+
+#[builder]
+struct Example {
+    // No attributes
+    string: String,
+
+    // No attributes
+    path_buf: std::path::PathBuf,
+
+    // No attributes
+    ip_addr: std::net::IpAddr,
+}
+
+Example::builder()
+    // We have to convert `&str -> String` manually
+    .string("string literal".to_owned())
+    // We have to convert `&str -> PathBuf` manually
+    .path_buf("string/literal".into())
+    // We have to convert `[u8; 4] -> IpAddr` manually
+    .ip_addr([127, 0, 0, 1].into())
+    .build();
+```
+:::
+
+### Automatic enum wrapping
+
+If you are working with enums a lot, you may implement the `From<EnumVariant>` for your enum and avoid wrapping your enum variants when passing them to the builder. Pay attention to the difference in the focused code below.
+
+::: code-group
+
+```rust [Into Conversions]
+use bon::builder;
+
+#[builder]
+fn evaluate(#[builder(into)] expr: Expr) { /* */ }  // [!code focus]
+
+evaluate()
+    .expr(BinaryExpr { // [!code focus]
+        /* */          // [!code focus]
+    })                 // [!code focus]
+    .call();
+
+enum Expr {
+    Binary(BinaryExpr),
+    Unary(UnaryExpr)
+}
+
+struct BinaryExpr { /* */ }
+struct UnaryExpr { /* */ }
+
+impl From<BinaryExpr> for Expr {
+    fn from(expr: BinaryExpr) -> Self {
+        Self::Binary(expr)
+    }
+}
+
+impl From<UnaryExpr> for Expr {
+    fn from(expr: UnaryExpr) -> Self {
+        Self::Unary(expr)
+    }
+}
+```
+
+```rust [Default]
+use bon::builder;
+
+#[builder]
+fn evaluate(expr: Expr) { /* */ }  // [!code focus]
+
+evaluate()
+    .expr(Expr::Binary(BinaryExpr {  // [!code focus]
+        /* */                        // [!code focus]
+    }))                              // [!code focus]
+    .call();
+
+enum Expr {
+    Binary(BinaryExpr),
+    Unary(UnaryExpr)
+}
+
+struct BinaryExpr { /* */ }
+struct UnaryExpr { /* */ }
+
+impl From<BinaryExpr> for Expr {
+    fn from(expr: BinaryExpr) -> Self {
+        Self::Binary(expr)
+    }
+}
+
+impl From<UnaryExpr> for Expr {
+    fn from(expr: UnaryExpr) -> Self {
+        Self::Unary(expr)
+    }
+}
+```
+
+:::
+
+As you can see, the difference isn't significant in this case. It makes more sense when you have deeply nested enums.
+
+## Avoid `Into` conversions
+
+### Performance-sensitive code
+
+If allocations can pose a bottleneck for your application and you need to see every place in code where an allocation is performed, you should avoid using `impl Into` overall. It can lead to implicitly moving data to the heap or cloning it.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder]
+fn process_heavy_json(#[builder(into)] data: String) { /* */ }
+
+let json = String::from(
+    r#"{
+        "key": "Pretend this is a huge JSON string with hundreds of MB in size"
+    }"#
+);
+
+process_heavy_json()
+    // Whoops, we passed a `&String`.              // [!code error]
+    // The builder will clone the data internally. // [!code error]
+    .data(&json)                                   // [!code error]
+    .call();
+```
+
+The problem here is that we unintentionally passed a `String` by reference instead of moving the ownership of the `String` to `process_heavy_json()`. This code implicitly uses [this `From` impl](https://github.com/rust-lang/rust/blob/1a94d839be8b248b972b9e022cb940d56de72fa1/library/alloc/src/string.rs#L2774-L2784) from the standard library.
+
+### Primitive numeric literals
+
+`impl Into` breaks type inference for numeric literal values. For example, the following code doesn't compile.
+
+```rust compile_fail
+fn half(x: impl Into<u32>) -> u32 {
+    x.into() / 2
+}
+
+half(10); // [!code error]
+```
+
+The compile error is the following ([Rust playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6b1b38e0de6f7747dc1ea3975fcffc06)):
+```log
+half(10);
+---- ^^ the trait `std::convert::From<i32>` is not implemented for `u32`,
+|       which is required by `{integer}: std::convert::Into<u32>`
+|
+required by a bound introduced by this call
+```
+
+The reason for this error is that `rustc` can't infer the type for the numeric literal `10`, because it could be one of the following types: `u8`, `u16`, `u32`, which all implement `Into<u32>`. There isn't a suffix like `10_u16` in this code to tell the compiler the type of the numeric literal `10`. When the compiler can't infer the type of a numeric literal it falls back to assigning the type `i32` for an integer literal and `f64` for a floating point literal. In this case `i32` is inferred, which isn't convertible to `u32`.
+
+Requiring an explicit type suffix in numeric literals would be the opposite of good ergonomics that `impl Into` is trying to achieve in the first place.
+
+::: info NOTE
+
+Type inference for primitive numeric types falls into the category of the [section below](#weakened-type-inference), but it's separated from it because this kind of type inference is generally less known to fail, so people usually forget about it because it always works for them.
+
+:::
+
+### Weakened type inference
+
+If you have a function that returns a generic type, then the compiler needs to infer that generic type from usage unless it's specified explicitly. A classic example of such a function is [`str::parse()`](https://doc.rust-lang.org/stable/std/primitive.str.html#method.parse) or [`serde_json::from_str()`](https://docs.rs/serde_json/latest/serde_json/fn.from_str.html).
+
+**Example:**
+
+```rust
+use bon::builder;
+use std::net::IpAddr;
+
+#[builder]
+fn connect(ip_addr: IpAddr) { /* */ }
+
+let ip_addr = "127.0.0.1".parse().unwrap();
+
+connect()
+    .ip_addr(ip_addr)
+    .call();
+```
+Notice how we didn't add a type annotation for the variable `ip_addr`. The compiler can deduce (infer) the type of `ip_addr` because it sees that the variable is passed to the `ip_addr()` setter method that expects a parameter of type `IpAddr`. It's a really simple exercise for the compiler in this case because all the context to solve it is there.
+
+However, if you use an `Into` conversion, not even Sherlock Holmes can answer the question "What type did you intend to parse?":
+
+```rust ignore
+use bon::builder;
+use std::net::IpAddr;
+
+#[builder]
+fn connect(ip_addr: IpAddr) { /* */ }                  // [!code --]
+fn connect(#[builder(into)] ip_addr: IpAddr) { /* */ } // [!code ++]
+
+let ip_addr = "127.0.0.1".parse().unwrap();
+
+connect()
+    .ip_addr(ip_addr)
+    .call();
+```
+
+In this case, there is a compile error:
+
+```log
+error[E0284]: type annotations needed                                    // [!code error]
+  |                                                                      // [!code error]
+9 |     let ip_addr = "127.0.0.1".parse().unwrap();                      // [!code error]
+  |         ^^^^^^^               ----- type must be known at this point // [!code error]
+  |                                                                      // [!code error]
+  = note: cannot satisfy `<_ as std::str::FromStr>::Err == _`            // [!code error]
+help: consider giving `ip_addr` an explicit type                         // [!code error]
+  |                                                                      // [!code error]
+9 |     let ip_addr: /* Type */ = "127.0.0.1".parse().unwrap();          // [!code error]
+  |                ++++++++++++                                          // [!code error]
+```
+
+This is because now the `ip_addr` setter looks like this:
+
+```rust ignore
+fn ip_addr(self, value: impl Into<IpAddr>) -> NextBuilderState { /* */ }
+```
+
+This signature implies that the `value` parameter can be of any type that implements `Into<IpAddr>`. There are several types that implement such a trait. Among them: [`Ipv4Addr`](https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#impl-From%3CIpv4Addr%3E-for-IpAddr) and [`Ipv6Addr`](https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#impl-From%3CIpv6Addr%3E-for-IpAddr), and, obviously, `IpAddr` itself (thanks to [this blanket impl](https://github.com/rust-lang/rust/blob/1a94d839be8b248b972b9e022cb940d56de72fa1/library/core/src/convert/mod.rs#L763-L771)).
+
+This means the setter for `ip_addr` can no longer hint the compiler a single type that it accepts. Thus the compiler can't decide which type to assign to the `ip_addr` variable in the original code, because *there can be many that make sense*. I.e. the code will compile if any of the `Ipv4Addr` or `Ipv6Addr` or `IpAddr` type annotations are added to the `ip_addr` variable, but the compiler has no right to decide which of them to use on your behalf.
+
+This is the drawback of using not only `impl Into`, but any generics at all.
+
+### Code complexity
+
+This is quite subjective, but `impl Into<T>` is a bit harder to read than just `T`. It makes the signature of the setter slightly bigger and requires you to understand what the `impl Trait` does, and what its implications are.
+
+If you want to keep your code simpler and more accessible (especially for beginner rustaceans), just avoid the `Into` conversions.
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/docs/guide/troubleshooting.md b/website/guide/troubleshooting.md
similarity index 100%
rename from website/docs/guide/troubleshooting.md
rename to website/guide/troubleshooting.md
diff --git a/website/index.md b/website/index.md
index 54151d48..57f13950 100644
--- a/website/index.md
+++ b/website/index.md
@@ -10,8 +10,8 @@ hero:
     src: /bon-logo-medium.png
   actions:
     - theme: brand
-      text: Docs
-      link: /docs/guide/overview
+      text: Guide
+      link: /guide/overview
     - theme: alt
       text: Blog
       link: /blog
diff --git a/website/package-lock.json b/website/package-lock.json
index 934b560b..580694be 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -9,13 +9,11 @@
         "vue": "^3.4.35"
       },
       "devDependencies": {
-        "@mdit/plugin-abbr": "^0.12.0",
+        "@mdit/plugin-abbr": "^0.13.0",
         "@types/node": "^20.14.11",
-        "gray-matter": "^4.0.3",
         "medium-zoom": "^1.1.0",
-        "remove-markdown": "^0.5.0",
         "smol-toml": "^1.3.0",
-        "vitepress": "^1.3.1"
+        "vitepress": "^1.3.4"
       }
     },
     "node_modules/@algolia/autocomplete-core": {
@@ -98,6 +96,27 @@
         "@algolia/transporter": "4.24.0"
       }
     },
+    "node_modules/@algolia/client-account/node_modules/@algolia/client-common": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz",
+      "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/@algolia/client-account/node_modules/@algolia/client-search": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz",
+      "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.24.0",
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
     "node_modules/@algolia/client-analytics": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz",
@@ -110,7 +129,7 @@
         "@algolia/transporter": "4.24.0"
       }
     },
-    "node_modules/@algolia/client-common": {
+    "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz",
       "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==",
@@ -120,6 +139,27 @@
         "@algolia/transporter": "4.24.0"
       }
     },
+    "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz",
+      "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.24.0",
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/@algolia/client-common": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.1.1.tgz",
+      "integrity": "sha512-jkQNQbGY+XQB3Eln7wqqdUZKBzG8lETcsaUk5gcMc6iIwyN/qW0v0fhpKPH+Kli+BImLxo0CWk12CvVvx2exWA==",
+      "dev": true,
+      "peer": true,
+      "engines": {
+        "node": ">= 14.0.0"
+      }
+    },
     "node_modules/@algolia/client-personalization": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz",
@@ -131,17 +171,31 @@
         "@algolia/transporter": "4.24.0"
       }
     },
-    "node_modules/@algolia/client-search": {
+    "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": {
       "version": "4.24.0",
-      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz",
-      "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz",
+      "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==",
       "dev": true,
       "dependencies": {
-        "@algolia/client-common": "4.24.0",
         "@algolia/requester-common": "4.24.0",
         "@algolia/transporter": "4.24.0"
       }
     },
+    "node_modules/@algolia/client-search": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.1.1.tgz",
+      "integrity": "sha512-SFpb3FI/VouGou/vpuS7qeCA5Y/KpV42P6CEA/1MZQtl/xJkl6PVjikb+Q9YadeHi2jtDV/aQ6PyiVDnX4PQcw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@algolia/client-common": "5.1.1",
+        "@algolia/requester-browser-xhr": "5.1.1",
+        "@algolia/requester-node-http": "5.1.1"
+      },
+      "engines": {
+        "node": ">= 14.0.0"
+      }
+    },
     "node_modules/@algolia/logger-common": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz",
@@ -176,7 +230,28 @@
         "@algolia/transporter": "4.24.0"
       }
     },
-    "node_modules/@algolia/requester-browser-xhr": {
+    "node_modules/@algolia/recommend/node_modules/@algolia/client-common": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz",
+      "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/@algolia/recommend/node_modules/@algolia/client-search": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz",
+      "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.24.0",
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz",
       "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==",
@@ -185,6 +260,28 @@
         "@algolia/requester-common": "4.24.0"
       }
     },
+    "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz",
+      "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.24.0"
+      }
+    },
+    "node_modules/@algolia/requester-browser-xhr": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.1.1.tgz",
+      "integrity": "sha512-NXmN1ujJCj5GlJQaMK6DbdiXdcf6nhRef/X40lu9TYi71q9xTo/5RPMI0K2iOp6g07S26BrXFOz6RSV3Ny4LLw==",
+      "dev": true,
+      "peer": true,
+      "dependencies": {
+        "@algolia/client-common": "5.1.1"
+      },
+      "engines": {
+        "node": ">= 14.0.0"
+      }
+    },
     "node_modules/@algolia/requester-common": {
       "version": "4.24.0",
       "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz",
@@ -192,12 +289,16 @@
       "dev": true
     },
     "node_modules/@algolia/requester-node-http": {
-      "version": "4.24.0",
-      "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz",
-      "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==",
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.1.1.tgz",
+      "integrity": "sha512-xwrgnNTIzgxDEx6zuCKSKTPzQLA8fL/WZiVB6fRpIu5agLMjoAi0cWA5YSDbo+2FFxqVgLqKY/Jz6mKmWtY15Q==",
       "dev": true,
+      "peer": true,
       "dependencies": {
-        "@algolia/requester-common": "4.24.0"
+        "@algolia/client-common": "5.1.1"
+      },
+      "engines": {
+        "node": ">= 14.0.0"
       }
     },
     "node_modules/@algolia/transporter": {
@@ -228,11 +329,11 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.25.3",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
-      "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz",
+      "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==",
       "dependencies": {
-        "@babel/types": "^7.25.2"
+        "@babel/types": "^7.25.4"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -242,9 +343,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.25.2",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
-      "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
+      "version": "7.25.4",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz",
+      "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==",
       "dependencies": {
         "@babel/helper-string-parser": "^7.24.8",
         "@babel/helper-validator-identifier": "^7.24.7",
@@ -255,30 +356,30 @@
       }
     },
     "node_modules/@docsearch/css": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz",
-      "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==",
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz",
+      "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==",
       "dev": true
     },
     "node_modules/@docsearch/js": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz",
-      "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==",
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.1.tgz",
+      "integrity": "sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==",
       "dev": true,
       "dependencies": {
-        "@docsearch/react": "3.6.0",
+        "@docsearch/react": "3.6.1",
         "preact": "^10.0.0"
       }
     },
     "node_modules/@docsearch/react": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz",
-      "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==",
+      "version": "3.6.1",
+      "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz",
+      "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==",
       "dev": true,
       "dependencies": {
         "@algolia/autocomplete-core": "1.9.3",
         "@algolia/autocomplete-preset-algolia": "1.9.3",
-        "@docsearch/css": "3.6.0",
+        "@docsearch/css": "3.6.1",
         "algoliasearch": "^4.19.1"
       },
       "peerDependencies": {
@@ -676,12 +777,12 @@
       "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
     },
     "node_modules/@mdit/plugin-abbr": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@mdit/plugin-abbr/-/plugin-abbr-0.12.0.tgz",
-      "integrity": "sha512-CT+t+jPgAPhfWg3eL/GxsHGwPK5AnNsSLzZt32ikYkvSeOSpuoYHM8qYfzSr2FCsDpfsGEn1nNE12I4KTjyKIQ==",
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@mdit/plugin-abbr/-/plugin-abbr-0.13.0.tgz",
+      "integrity": "sha512-Ap1nevIG+pF98L1T9tOhVsrDSGX/b46l3LR4P9q9rE2cKrNHUn5xXRMsD72t1cktJT6RijbKSSnxA16PteDKxg==",
       "dev": true,
       "dependencies": {
-        "@types/markdown-it": "^14.1.1"
+        "@types/markdown-it": "^14.1.2"
       },
       "peerDependencies": {
         "markdown-it": "^14.1.0"
@@ -693,9 +794,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz",
-      "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz",
+      "integrity": "sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA==",
       "cpu": [
         "arm"
       ],
@@ -706,9 +807,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz",
-      "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz",
+      "integrity": "sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA==",
       "cpu": [
         "arm64"
       ],
@@ -719,9 +820,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz",
-      "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz",
+      "integrity": "sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA==",
       "cpu": [
         "arm64"
       ],
@@ -732,9 +833,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz",
-      "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz",
+      "integrity": "sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg==",
       "cpu": [
         "x64"
       ],
@@ -745,9 +846,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz",
-      "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz",
+      "integrity": "sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA==",
       "cpu": [
         "arm"
       ],
@@ -758,9 +859,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz",
-      "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz",
+      "integrity": "sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w==",
       "cpu": [
         "arm"
       ],
@@ -771,9 +872,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz",
-      "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz",
+      "integrity": "sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w==",
       "cpu": [
         "arm64"
       ],
@@ -784,9 +885,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz",
-      "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz",
+      "integrity": "sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw==",
       "cpu": [
         "arm64"
       ],
@@ -797,9 +898,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz",
-      "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz",
+      "integrity": "sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg==",
       "cpu": [
         "ppc64"
       ],
@@ -810,9 +911,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz",
-      "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz",
+      "integrity": "sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg==",
       "cpu": [
         "riscv64"
       ],
@@ -823,9 +924,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz",
-      "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz",
+      "integrity": "sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw==",
       "cpu": [
         "s390x"
       ],
@@ -836,9 +937,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz",
-      "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz",
+      "integrity": "sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg==",
       "cpu": [
         "x64"
       ],
@@ -849,9 +950,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz",
-      "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz",
+      "integrity": "sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ==",
       "cpu": [
         "x64"
       ],
@@ -862,9 +963,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz",
-      "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz",
+      "integrity": "sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ==",
       "cpu": [
         "arm64"
       ],
@@ -875,9 +976,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz",
-      "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz",
+      "integrity": "sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg==",
       "cpu": [
         "ia32"
       ],
@@ -888,9 +989,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz",
-      "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz",
+      "integrity": "sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ==",
       "cpu": [
         "x64"
       ],
@@ -901,21 +1002,21 @@
       ]
     },
     "node_modules/@shikijs/core": {
-      "version": "1.10.3",
-      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.10.3.tgz",
-      "integrity": "sha512-D45PMaBaeDHxww+EkcDQtDAtzv00Gcsp72ukBtaLSmqRvh0WgGMq3Al0rl1QQBZfuneO75NXMIzEZGFitThWbg==",
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.14.1.tgz",
+      "integrity": "sha512-KyHIIpKNaT20FtFPFjCQB5WVSTpLR/n+jQXhWHWVUMm9MaOaG9BGOG0MSyt7yA4+Lm+4c9rTc03tt3nYzeYSfw==",
       "dev": true,
       "dependencies": {
         "@types/hast": "^3.0.4"
       }
     },
     "node_modules/@shikijs/transformers": {
-      "version": "1.10.3",
-      "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.10.3.tgz",
-      "integrity": "sha512-MNjsyye2WHVdxfZUSr5frS97sLGe6G1T+1P41QjyBFJehZphMcr4aBlRLmq6OSPBslYe9byQPVvt/LJCOfxw8Q==",
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.14.1.tgz",
+      "integrity": "sha512-JJqL8QBVCJh3L61jqqEXgFq1cTycwjcGj7aSmqOEsbxnETM9hRlaB74QuXvY/fVJNjbNt8nvWo0VwAXKvMSLRg==",
       "dev": true,
       "dependencies": {
-        "shiki": "1.10.3"
+        "shiki": "1.14.1"
       }
     },
     "node_modules/@types/estree": {
@@ -956,18 +1057,18 @@
       "dev": true
     },
     "node_modules/@types/node": {
-      "version": "20.14.11",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
-      "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
+      "version": "20.16.1",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz",
+      "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==",
       "dev": true,
       "dependencies": {
-        "undici-types": "~5.26.4"
+        "undici-types": "~6.19.2"
       }
     },
     "node_modules/@types/unist": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz",
-      "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+      "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
       "dev": true
     },
     "node_modules/@types/web-bluetooth": {
@@ -977,9 +1078,9 @@
       "dev": true
     },
     "node_modules/@vitejs/plugin-vue": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz",
-      "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz",
+      "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==",
       "dev": true,
       "engines": {
         "node": "^18.0.0 || >=20.0.0"
@@ -990,36 +1091,36 @@
       }
     },
     "node_modules/@vue/compiler-core": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.35.tgz",
-      "integrity": "sha512-gKp0zGoLnMYtw4uS/SJRRO7rsVggLjvot3mcctlMXunYNsX+aRJDqqw/lV5/gHK91nvaAAlWFgdVl020AW1Prg==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
+      "integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==",
       "dependencies": {
         "@babel/parser": "^7.24.7",
-        "@vue/shared": "3.4.35",
+        "@vue/shared": "3.4.38",
         "entities": "^4.5.0",
         "estree-walker": "^2.0.2",
         "source-map-js": "^1.2.0"
       }
     },
     "node_modules/@vue/compiler-dom": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.35.tgz",
-      "integrity": "sha512-pWIZRL76/oE/VMhdv/ovZfmuooEni6JPG1BFe7oLk5DZRo/ImydXijoZl/4kh2406boRQ7lxTYzbZEEXEhj9NQ==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz",
+      "integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==",
       "dependencies": {
-        "@vue/compiler-core": "3.4.35",
-        "@vue/shared": "3.4.35"
+        "@vue/compiler-core": "3.4.38",
+        "@vue/shared": "3.4.38"
       }
     },
     "node_modules/@vue/compiler-sfc": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.35.tgz",
-      "integrity": "sha512-xacnRS/h/FCsjsMfxBkzjoNxyxEyKyZfBch/P4vkLRvYJwe5ChXmZZrj8Dsed/752H2Q3JE8kYu9Uyha9J6PgA==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz",
+      "integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==",
       "dependencies": {
         "@babel/parser": "^7.24.7",
-        "@vue/compiler-core": "3.4.35",
-        "@vue/compiler-dom": "3.4.35",
-        "@vue/compiler-ssr": "3.4.35",
-        "@vue/shared": "3.4.35",
+        "@vue/compiler-core": "3.4.38",
+        "@vue/compiler-dom": "3.4.38",
+        "@vue/compiler-ssr": "3.4.38",
+        "@vue/shared": "3.4.38",
         "estree-walker": "^2.0.2",
         "magic-string": "^0.30.10",
         "postcss": "^8.4.40",
@@ -1027,30 +1128,30 @@
       }
     },
     "node_modules/@vue/compiler-ssr": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.35.tgz",
-      "integrity": "sha512-7iynB+0KB1AAJKk/biENTV5cRGHRdbdaD7Mx3nWcm1W8bVD6QmnH3B4AHhQQ1qZHhqFwzEzMwiytXm3PX1e60A==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz",
+      "integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.35",
-        "@vue/shared": "3.4.35"
+        "@vue/compiler-dom": "3.4.38",
+        "@vue/shared": "3.4.38"
       }
     },
     "node_modules/@vue/devtools-api": {
-      "version": "7.3.5",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.5.tgz",
-      "integrity": "sha512-BSdBBu5hOIv+gBJC9jzYMh5bC27FQwjWLSb8fVAniqlL9gvsqvK27xTgczMf+hgctlszMYQnRm3bpY/j8vhPqw==",
+      "version": "7.3.8",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.8.tgz",
+      "integrity": "sha512-NURFwmxz4WukFU54IHgyGI2KSejdgHG5JC4xTcWmTWEBIc8aelj9fBy4qsboObGHFp3JIdRxxANO9s2wZA/pVQ==",
       "dev": true,
       "dependencies": {
-        "@vue/devtools-kit": "^7.3.5"
+        "@vue/devtools-kit": "^7.3.8"
       }
     },
     "node_modules/@vue/devtools-kit": {
-      "version": "7.3.5",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.5.tgz",
-      "integrity": "sha512-wwfi10gJ1HMtjzcd8aIOnzBHlIRqsYDgcDyrKvkeyc0Gbcoe7UrkXRVHZUOtcxxoplHA0PwpT6wFg0uUCmi8Ww==",
+      "version": "7.3.8",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.8.tgz",
+      "integrity": "sha512-HYy3MQP1nZ6GbE4vrgJ/UB+MvZnhYmEwCa/UafrEpdpwa+jNCkz1ZdUrC5I7LpkH1ShREEV2/pZlAQdBj+ncLQ==",
       "dev": true,
       "dependencies": {
-        "@vue/devtools-shared": "^7.3.5",
+        "@vue/devtools-shared": "^7.3.8",
         "birpc": "^0.2.17",
         "hookable": "^5.5.3",
         "mitt": "^3.0.1",
@@ -1060,78 +1161,78 @@
       }
     },
     "node_modules/@vue/devtools-shared": {
-      "version": "7.3.5",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.5.tgz",
-      "integrity": "sha512-Rqii3VazmWTi67a86rYopi61n5Ved05EybJCwyrfoO9Ok3MaS/4yRFl706ouoISMlyrASJFEzM0/AiDA6w4f9A==",
+      "version": "7.3.8",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.8.tgz",
+      "integrity": "sha512-1NiJbn7Yp47nPDWhFZyEKpB2+5/+7JYv8IQnU0ccMrgslPR2dL7u1DIyI7mLqy4HN1ll36gQy0k8GqBYSFgZJw==",
       "dev": true,
       "dependencies": {
         "rfdc": "^1.4.1"
       }
     },
     "node_modules/@vue/reactivity": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.35.tgz",
-      "integrity": "sha512-Ggtz7ZZHakriKioveJtPlStYardwQH6VCs9V13/4qjHSQb/teE30LVJNrbBVs4+aoYGtTQKJbTe4CWGxVZrvEw==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz",
+      "integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==",
       "dependencies": {
-        "@vue/shared": "3.4.35"
+        "@vue/shared": "3.4.38"
       }
     },
     "node_modules/@vue/runtime-core": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.35.tgz",
-      "integrity": "sha512-D+BAjFoWwT5wtITpSxwqfWZiBClhBbR+bm0VQlWYFOadUUXFo+5wbe9ErXhLvwguPiLZdEF13QAWi2vP3ZD5tA==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz",
+      "integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==",
       "dependencies": {
-        "@vue/reactivity": "3.4.35",
-        "@vue/shared": "3.4.35"
+        "@vue/reactivity": "3.4.38",
+        "@vue/shared": "3.4.38"
       }
     },
     "node_modules/@vue/runtime-dom": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.35.tgz",
-      "integrity": "sha512-yGOlbos+MVhlS5NWBF2HDNgblG8e2MY3+GigHEyR/dREAluvI5tuUUgie3/9XeqhPE4LF0i2wjlduh5thnfOqw==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz",
+      "integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==",
       "dependencies": {
-        "@vue/reactivity": "3.4.35",
-        "@vue/runtime-core": "3.4.35",
-        "@vue/shared": "3.4.35",
+        "@vue/reactivity": "3.4.38",
+        "@vue/runtime-core": "3.4.38",
+        "@vue/shared": "3.4.38",
         "csstype": "^3.1.3"
       }
     },
     "node_modules/@vue/server-renderer": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.35.tgz",
-      "integrity": "sha512-iZ0e/u9mRE4T8tNhlo0tbA+gzVkgv8r5BX6s1kRbOZqfpq14qoIvCZ5gIgraOmYkMYrSEZgkkojFPr+Nyq/Mnw==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz",
+      "integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==",
       "dependencies": {
-        "@vue/compiler-ssr": "3.4.35",
-        "@vue/shared": "3.4.35"
+        "@vue/compiler-ssr": "3.4.38",
+        "@vue/shared": "3.4.38"
       },
       "peerDependencies": {
-        "vue": "3.4.35"
+        "vue": "3.4.38"
       }
     },
     "node_modules/@vue/shared": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.35.tgz",
-      "integrity": "sha512-hvuhBYYDe+b1G8KHxsQ0diDqDMA8D9laxWZhNAjE83VZb5UDaXl9Xnz7cGdDSyiHM90qqI/CyGMcpBpiDy6VVQ=="
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz",
+      "integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw=="
     },
     "node_modules/@vueuse/core": {
-      "version": "10.11.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz",
-      "integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==",
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.0.1.tgz",
+      "integrity": "sha512-YTrekI18WwEyP3h168Fir94G/HNC27wvXJI21Alm0sPOwvhihfkrvHIe+5PNJq+MpgWdRcsjvE/38JaoKrgZhQ==",
       "dev": true,
       "dependencies": {
         "@types/web-bluetooth": "^0.0.20",
-        "@vueuse/metadata": "10.11.0",
-        "@vueuse/shared": "10.11.0",
-        "vue-demi": ">=0.14.8"
+        "@vueuse/metadata": "11.0.1",
+        "@vueuse/shared": "11.0.1",
+        "vue-demi": ">=0.14.10"
       },
       "funding": {
         "url": "https://github.com/sponsors/antfu"
       }
     },
     "node_modules/@vueuse/core/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
@@ -1155,14 +1256,14 @@
       }
     },
     "node_modules/@vueuse/integrations": {
-      "version": "10.11.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.0.tgz",
-      "integrity": "sha512-Pp6MtWEIr+NDOccWd8j59Kpjy5YDXogXI61Kb1JxvSfVBO8NzFQkmrKmSZz47i+ZqHnIzxaT38L358yDHTncZg==",
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.0.1.tgz",
+      "integrity": "sha512-V/FQTS/aiV6RTFXOj8cXgqhtNJBvxvbHeLElOUR7N7F3Kr0btS+dkymLB54mFd0Or6uEGpgwwb41cs/q2/rdOg==",
       "dev": true,
       "dependencies": {
-        "@vueuse/core": "10.11.0",
-        "@vueuse/shared": "10.11.0",
-        "vue-demi": ">=0.14.8"
+        "@vueuse/core": "11.0.1",
+        "@vueuse/shared": "11.0.1",
+        "vue-demi": ">=0.14.10"
       },
       "funding": {
         "url": "https://github.com/sponsors/antfu"
@@ -1170,16 +1271,16 @@
       "peerDependencies": {
         "async-validator": "^4",
         "axios": "^1",
-        "change-case": "^4",
-        "drauu": "^0.3",
+        "change-case": "^5",
+        "drauu": "^0.4",
         "focus-trap": "^7",
-        "fuse.js": "^6",
+        "fuse.js": "^7",
         "idb-keyval": "^6",
-        "jwt-decode": "^3",
+        "jwt-decode": "^4",
         "nprogress": "^0.2",
         "qrcode": "^1.5",
         "sortablejs": "^1",
-        "universal-cookie": "^6"
+        "universal-cookie": "^7"
       },
       "peerDependenciesMeta": {
         "async-validator": {
@@ -1221,9 +1322,9 @@
       }
     },
     "node_modules/@vueuse/integrations/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
@@ -1247,30 +1348,30 @@
       }
     },
     "node_modules/@vueuse/metadata": {
-      "version": "10.11.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz",
-      "integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==",
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.0.1.tgz",
+      "integrity": "sha512-dTFvuHFAjLYOiSd+t9Sk7xUiuL6jbfay/eX+g+jaipXXlwKur2VCqBCZX+jfu+2vROUGcUsdn3fJR9KkpadIOg==",
       "dev": true,
       "funding": {
         "url": "https://github.com/sponsors/antfu"
       }
     },
     "node_modules/@vueuse/shared": {
-      "version": "10.11.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz",
-      "integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==",
+      "version": "11.0.1",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.0.1.tgz",
+      "integrity": "sha512-eAPf5CQB3HR0S76HqrhjBqFYstZfiHWZq8xF9EQmobGBkrhPfErJEhr8aMNQMqd6MkENIx2pblIEfJGlHpClug==",
       "dev": true,
       "dependencies": {
-        "vue-demi": ">=0.14.8"
+        "vue-demi": ">=0.14.10"
       },
       "funding": {
         "url": "https://github.com/sponsors/antfu"
       }
     },
     "node_modules/@vueuse/shared/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.10",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+      "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
@@ -1316,13 +1417,43 @@
         "@algolia/transporter": "4.24.0"
       }
     },
-    "node_modules/argparse": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
-      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+    "node_modules/algoliasearch/node_modules/@algolia/client-common": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz",
+      "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/algoliasearch/node_modules/@algolia/client-search": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz",
+      "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/client-common": "4.24.0",
+        "@algolia/requester-common": "4.24.0",
+        "@algolia/transporter": "4.24.0"
+      }
+    },
+    "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz",
+      "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==",
+      "dev": true,
+      "dependencies": {
+        "@algolia/requester-common": "4.24.0"
+      }
+    },
+    "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": {
+      "version": "4.24.0",
+      "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz",
+      "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==",
       "dev": true,
       "dependencies": {
-        "sprintf-js": "~1.0.2"
+        "@algolia/requester-common": "4.24.0"
       }
     },
     "node_modules/birpc": {
@@ -1403,36 +1534,11 @@
         "@esbuild/win32-x64": "0.21.5"
       }
     },
-    "node_modules/esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
-      "dev": true,
-      "bin": {
-        "esparse": "bin/esparse.js",
-        "esvalidate": "bin/esvalidate.js"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
       "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
-    "node_modules/extend-shallow": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
-      "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
-      "dev": true,
-      "dependencies": {
-        "is-extendable": "^0.1.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/focus-trap": {
       "version": "7.5.4",
       "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz",
@@ -1456,36 +1562,12 @@
         "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
       }
     },
-    "node_modules/gray-matter": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
-      "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
-      "dev": true,
-      "dependencies": {
-        "js-yaml": "^3.13.1",
-        "kind-of": "^6.0.2",
-        "section-matter": "^1.0.0",
-        "strip-bom-string": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=6.0"
-      }
-    },
     "node_modules/hookable": {
       "version": "5.5.3",
       "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
       "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
       "dev": true
     },
-    "node_modules/is-extendable": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
-      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/is-what": {
       "version": "4.1.16",
       "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
@@ -1498,28 +1580,6 @@
         "url": "https://github.com/sponsors/mesqueeb"
       }
     },
-    "node_modules/js-yaml": {
-      "version": "3.14.1",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
-      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
-      "dev": true,
-      "dependencies": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
-      },
-      "bin": {
-        "js-yaml": "bin/js-yaml.js"
-      }
-    },
-    "node_modules/kind-of": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
-      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/magic-string": {
       "version": "0.30.11",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
@@ -1541,9 +1601,9 @@
       "dev": true
     },
     "node_modules/minisearch": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.0.0.tgz",
-      "integrity": "sha512-0OIJ3hUE+YBJNruDCqbTMFmk/IoB1CpZzuGfl11khFIel66ew9UoLF/+gfq3bdyrneqr3P7BTjFZApUbmk+9Dg==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz",
+      "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==",
       "dev": true
     },
     "node_modules/mitt": {
@@ -1581,9 +1641,9 @@
       "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
     },
     "node_modules/postcss": {
-      "version": "8.4.40",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
-      "integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
+      "version": "8.4.41",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
+      "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==",
       "funding": [
         {
           "type": "opencollective",
@@ -1608,21 +1668,15 @@
       }
     },
     "node_modules/preact": {
-      "version": "10.22.1",
-      "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz",
-      "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==",
+      "version": "10.23.2",
+      "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz",
+      "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==",
       "dev": true,
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/preact"
       }
     },
-    "node_modules/remove-markdown": {
-      "version": "0.5.0",
-      "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz",
-      "integrity": "sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==",
-      "dev": true
-    },
     "node_modules/rfdc": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
@@ -1630,9 +1684,9 @@
       "dev": true
     },
     "node_modules/rollup": {
-      "version": "4.18.1",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz",
-      "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==",
+      "version": "4.21.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.0.tgz",
+      "integrity": "sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ==",
       "dev": true,
       "dependencies": {
         "@types/estree": "1.0.5"
@@ -1645,52 +1699,39 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.18.1",
-        "@rollup/rollup-android-arm64": "4.18.1",
-        "@rollup/rollup-darwin-arm64": "4.18.1",
-        "@rollup/rollup-darwin-x64": "4.18.1",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.18.1",
-        "@rollup/rollup-linux-arm-musleabihf": "4.18.1",
-        "@rollup/rollup-linux-arm64-gnu": "4.18.1",
-        "@rollup/rollup-linux-arm64-musl": "4.18.1",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1",
-        "@rollup/rollup-linux-riscv64-gnu": "4.18.1",
-        "@rollup/rollup-linux-s390x-gnu": "4.18.1",
-        "@rollup/rollup-linux-x64-gnu": "4.18.1",
-        "@rollup/rollup-linux-x64-musl": "4.18.1",
-        "@rollup/rollup-win32-arm64-msvc": "4.18.1",
-        "@rollup/rollup-win32-ia32-msvc": "4.18.1",
-        "@rollup/rollup-win32-x64-msvc": "4.18.1",
+        "@rollup/rollup-android-arm-eabi": "4.21.0",
+        "@rollup/rollup-android-arm64": "4.21.0",
+        "@rollup/rollup-darwin-arm64": "4.21.0",
+        "@rollup/rollup-darwin-x64": "4.21.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.21.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.21.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.21.0",
+        "@rollup/rollup-linux-arm64-musl": "4.21.0",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.21.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.21.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.21.0",
+        "@rollup/rollup-linux-x64-gnu": "4.21.0",
+        "@rollup/rollup-linux-x64-musl": "4.21.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.21.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.21.0",
+        "@rollup/rollup-win32-x64-msvc": "4.21.0",
         "fsevents": "~2.3.2"
       }
     },
     "node_modules/search-insights": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.15.0.tgz",
-      "integrity": "sha512-ch2sPCUDD4sbPQdknVl9ALSi9H7VyoeVbsxznYz6QV55jJ8CI3EtwpO1i84keN4+hF5IeHWIeGvc08530JkVXQ==",
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.0.tgz",
+      "integrity": "sha512-AskayU3QNsXQzSL6v4LTYST7NNfs2HWyHHB+sdORP9chsytAhro5XRfToAMI/LAVYgNbzowVZTMfBRodgbUHKg==",
       "dev": true,
       "peer": true
     },
-    "node_modules/section-matter": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
-      "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
-      "dev": true,
-      "dependencies": {
-        "extend-shallow": "^2.0.1",
-        "kind-of": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/shiki": {
-      "version": "1.10.3",
-      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.10.3.tgz",
-      "integrity": "sha512-eneCLncGuvPdTutJuLyUGS8QNPAVFO5Trvld2wgEq1e002mwctAhJKeMGWtWVXOIEzmlcLRqcgPSorR6AVzOmQ==",
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.14.1.tgz",
+      "integrity": "sha512-FujAN40NEejeXdzPt+3sZ3F2dx1U24BY2XTY01+MG8mbxCiA2XukXdcbyMyLAHJ/1AUUnQd1tZlvIjefWWEJeA==",
       "dev": true,
       "dependencies": {
-        "@shikijs/core": "1.10.3",
+        "@shikijs/core": "1.14.1",
         "@types/hast": "^3.0.4"
       }
     },
@@ -1723,21 +1764,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/sprintf-js": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
-      "dev": true
-    },
-    "node_modules/strip-bom-string": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
-      "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/superjson": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz",
@@ -1765,20 +1791,20 @@
       }
     },
     "node_modules/undici-types": {
-      "version": "5.26.5",
-      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
-      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "version": "6.19.8",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
       "dev": true
     },
     "node_modules/vite": {
-      "version": "5.3.3",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz",
-      "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==",
+      "version": "5.4.2",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
+      "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
       "dev": true,
       "dependencies": {
         "esbuild": "^0.21.3",
-        "postcss": "^8.4.39",
-        "rollup": "^4.13.0"
+        "postcss": "^8.4.41",
+        "rollup": "^4.20.0"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -1797,6 +1823,7 @@
         "less": "*",
         "lightningcss": "^1.21.0",
         "sass": "*",
+        "sass-embedded": "*",
         "stylus": "*",
         "sugarss": "*",
         "terser": "^5.4.0"
@@ -1814,6 +1841,9 @@
         "sass": {
           "optional": true
         },
+        "sass-embedded": {
+          "optional": true
+        },
         "stylus": {
           "optional": true
         },
@@ -1826,27 +1856,27 @@
       }
     },
     "node_modules/vitepress": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.1.tgz",
-      "integrity": "sha512-soZDpg2rRVJNIM/IYMNDPPr+zTHDA5RbLDHAxacRu+Q9iZ2GwSR0QSUlLs+aEZTkG0SOX1dc8RmUYwyuxK8dfQ==",
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.4.tgz",
+      "integrity": "sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ==",
       "dev": true,
       "dependencies": {
-        "@docsearch/css": "^3.6.0",
-        "@docsearch/js": "^3.6.0",
-        "@shikijs/core": "^1.10.3",
-        "@shikijs/transformers": "^1.10.3",
-        "@types/markdown-it": "^14.1.1",
-        "@vitejs/plugin-vue": "^5.0.5",
-        "@vue/devtools-api": "^7.3.5",
-        "@vue/shared": "^3.4.31",
-        "@vueuse/core": "^10.11.0",
-        "@vueuse/integrations": "^10.11.0",
+        "@docsearch/css": "^3.6.1",
+        "@docsearch/js": "^3.6.1",
+        "@shikijs/core": "^1.13.0",
+        "@shikijs/transformers": "^1.13.0",
+        "@types/markdown-it": "^14.1.2",
+        "@vitejs/plugin-vue": "^5.1.2",
+        "@vue/devtools-api": "^7.3.8",
+        "@vue/shared": "^3.4.38",
+        "@vueuse/core": "^11.0.0",
+        "@vueuse/integrations": "^11.0.0",
         "focus-trap": "^7.5.4",
         "mark.js": "8.11.1",
-        "minisearch": "^7.0.0",
-        "shiki": "^1.10.3",
-        "vite": "^5.3.3",
-        "vue": "^3.4.31"
+        "minisearch": "^7.1.0",
+        "shiki": "^1.13.0",
+        "vite": "^5.4.1",
+        "vue": "^3.4.38"
       },
       "bin": {
         "vitepress": "bin/vitepress.js"
@@ -1865,15 +1895,15 @@
       }
     },
     "node_modules/vue": {
-      "version": "3.4.35",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.35.tgz",
-      "integrity": "sha512-+fl/GLmI4GPileHftVlCdB7fUL4aziPcqTudpTGXCT8s+iZWuOCeNEB5haX6Uz2IpRrbEXOgIFbe+XciCuGbNQ==",
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
+      "integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==",
       "dependencies": {
-        "@vue/compiler-dom": "3.4.35",
-        "@vue/compiler-sfc": "3.4.35",
-        "@vue/runtime-dom": "3.4.35",
-        "@vue/server-renderer": "3.4.35",
-        "@vue/shared": "3.4.35"
+        "@vue/compiler-dom": "3.4.38",
+        "@vue/compiler-sfc": "3.4.38",
+        "@vue/runtime-dom": "3.4.38",
+        "@vue/server-renderer": "3.4.38",
+        "@vue/shared": "3.4.38"
       },
       "peerDependencies": {
         "typescript": "*"
diff --git a/website/package.json b/website/package.json
index 5379913e..12d552fd 100644
--- a/website/package.json
+++ b/website/package.json
@@ -8,13 +8,11 @@
     "preview": "vitepress preview"
   },
   "devDependencies": {
-    "@mdit/plugin-abbr": "^0.12.0",
+    "@mdit/plugin-abbr": "^0.13.0",
     "@types/node": "^20.14.11",
-    "gray-matter": "^4.0.3",
-    "remove-markdown": "^0.5.0",
+    "medium-zoom": "^1.1.0",
     "smol-toml": "^1.3.0",
-    "vitepress": "^1.3.1",
-    "medium-zoom": "^1.1.0"
+    "vitepress": "^1.3.4"
   },
   "dependencies": {
     "vue": "^3.4.35"
diff --git a/website/docs/reference/bon.md b/website/reference/bon.md
similarity index 100%
rename from website/docs/reference/bon.md
rename to website/reference/bon.md
diff --git a/website/reference/builder.md b/website/reference/builder.md
new file mode 100644
index 00000000..9135b1bf
--- /dev/null
+++ b/website/reference/builder.md
@@ -0,0 +1,867 @@
+---
+outline: [2, 3]
+---
+
+# `#[builder]`
+
+## Top-Level Attributes
+
+### `builder_type`
+
+**Applies to:** <Badge text="structs"/> <Badge text="free functions"/> <Badge text="associated methods"/>
+
+Overrides the name of the generated builder struct.
+
+The default naming pattern is the following:
+
+| Type of item `#[builder]` is placed on | Naming pattern                                |
+| -------------------------------------- | --------------------------------------------- |
+| Struct                                 | `{StructName}Builder`                         |
+| `StructName::new()` method             | `{StructName}Builder`                         |
+| Free function                          | `{PascalCaseFunctionName}Builder`             |
+| Associated method                      | `{SelfTypeName}{PascalCaseMethodName}Builder` |
+
+The attribute expects the desired builder type identifier as its input.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct]
+use bon::builder;
+
+#[builder(builder_type = MyBuilder)] // [!code highlight]
+struct Brush {}
+
+let builder: MyBuilder = Brush::builder();
+```
+
+```rust [Free function]
+use bon::builder;
+
+#[builder(builder_type = MyBuilder)] // [!code highlight]
+fn brush() {}
+
+let builder: MyBuilder = brush();
+```
+
+```rust [Associated method]
+use bon::bon;
+
+struct Brush;
+
+#[bon]
+impl Brush {
+    #[builder(builder_type = MyBuilder)] // [!code highlight]
+    fn new() -> Self {
+        Self
+    }
+}
+
+let builder: MyBuilder = Brush::builder();
+```
+
+:::
+
+You'll usually want to override the builder type name when you already have such a name in scope. For example, if you have a struct and a function with the same name annotated with `#[builder]`:
+
+::: code-group
+
+```rust compile_fail [Errored]
+use bon::builder;
+
+#[builder] // [!code error]
+struct Brush {}
+
+#[builder] // [!code error]
+fn brush() {}
+
+// `BrushBuilder` builder type name was generated for both
+// the struct and the function. This is a compile error
+let builder: BrushBuilder = Brush::builder();
+let builder: BrushBuilder = brush();
+```
+
+```rust [Fixed]
+use bon::builder;
+
+#[builder(builder_type = MyBuilder)] // [!code highlight]
+struct Brush {}
+
+#[builder]
+fn brush() {}
+
+// Now builder types are named differently
+let builder: MyBuilder = Brush::builder();
+let builder: BrushBuilder = brush();
+```
+
+:::
+
+### `expose_positional_fn`
+
+**Applies to:** <Badge text="free functions"/> <Badge text="associated methods"/>
+
+When generating builder code for functions the `#[builder]` macro hides the original function with positional parameters used to define the builder. That function is invoked inside of the builder's `call()` or `build()` method.
+
+Usually you'd want the underlying positional function to be hidden to provide only the builder syntax to the callers. However, in some situations you may want to keep the positional function exposed along with the builder syntax for compatibility with old code that still uses the old positional function call syntax.
+
+This attribute can take several forms.
+
+-   Simple: `#[builder(expose_positional_fn = identifier)]`. Sets only the name of the positional function.
+-   Verbose: `#[builder(expose_positional_fn(name = identifier, vis = "visibility"))]`.
+    Allows setting both the name and the visibility of the positional function.
+    Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility).
+
+If `vis` parameter is not specified, then the visibility of the exposed positional function will be the same as specified on the function that the `#[builder]` was applied to.
+
+**Example:**
+
+::: code-group
+
+```rust [Free function]
+use bon::builder;
+
+#[builder(expose_positional_fn = example_positional)] // [!code highlight]
+fn example(x: u32, y: u32) {}
+
+// Positional function is now available under the given name  // [!code highlight]
+example_positional(1, 2);                                     // [!code highlight]
+
+// Builder syntax is also available (unchanged)
+example()
+    .x(1)
+    .y(2)
+    .call();
+```
+
+```rust [Associated method]
+use bon::bon;
+
+struct Example;
+
+#[bon]
+impl Example {
+    #[builder(expose_positional_fn = example_positional)] // [!code highlight]
+    fn example(x: u32, y: u32) {}
+}
+
+// Positional function is now available under the given name  // [!code highlight]
+Example::example_positional(1, 2);                            // [!code highlight]
+
+// Builder syntax is also available (unchanged)
+Example::example()
+    .x(1)
+    .y(2)
+    .call();
+```
+
+:::
+
+#### `new` method special case
+
+There are two conventional names in Rust ecosystem for constructors and builders:
+
+-   `new` is used for a constructor method that uses positional parameters
+-   `builder` is used for a method that returns a builder for a type
+
+So when `#[builder]` is placed on a method called `new`, it'll generate a method called `builder` that starts the building process. This means there is already a default obvious name for the positional function that `expose_positional_fn` may use in this case if you don't specify any value for this attribute.
+
+**Example:**
+
+```rust
+use bon::bon;
+
+struct Example {
+    x: u32,
+    y: u32,
+}
+
+#[bon]
+impl Example {
+    #[builder(expose_positional_fn)] // [!code highlight]
+    fn new(x: u32, y: u32) -> Self {
+        Self { x, y }
+    }
+}
+
+// Positional function is available under the name `new` // [!code highlight]
+Example::new(1, 2);                                      // [!code highlight]
+
+// Builder syntax is also available (unchanged)
+Example::builder()
+    .x(1)
+    .y(2)
+    .build();
+```
+
+This makes it possible to add builder syntax to your existing types that have the `new` method without breaking compatibility with old code. Old code can still use `T::new()` syntax, while new code can benefit from `T::builder()` syntax.
+
+### `finish_fn`
+
+**Applies to:** <Badge text="structs"/> <Badge text="free functions"/> <Badge text="associated methods"/>
+
+This attribute allows overriding the name of the generated builder's method that finishes the building process.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct]
+use bon::builder;
+
+#[builder(finish_fn = assemble)] // [!code highlight]
+struct Article {
+    id: u32
+}
+
+let article = Article::builder()
+    .id(42)
+    .assemble(); // [!code highlight]
+
+assert_eq!(article.id, 42);
+```
+
+```rust [Free function]
+use bon::builder;
+
+#[builder(finish_fn = send)] // [!code highlight]
+fn get_article(id: u32) -> String {
+    format!("Some article with id {id}")
+}
+
+let response = get_article()
+    .id(42)
+    .send(); // [!code highlight]
+
+assert_eq!(response, "Some article with id 42");
+```
+
+```rust [Associated method]
+use bon::bon;
+
+struct ArticlesClient;
+
+#[bon]
+impl ArticlesClient {
+    #[builder(finish_fn = send)] // [!code highlight]
+    fn get_article(&self, id: u32) -> String {
+        format!("Some article with id {id}")
+    }
+}
+
+let response = ArticlesClient
+    .get_article()
+    .id(42)
+    .send(); // [!code highlight]
+
+assert_eq!(response, "Some article with id 42");
+```
+
+:::
+
+### `start_fn`
+
+**Applies to:** <Badge text="structs"/>
+
+Overrides the name and visibility of the associated method that starts the building process, i.e. returns the builder for the struct.
+
+The default name for this method is `builder`, and the default visibility is the same as the visibility of the struct itself.
+
+This attribute can take several forms.
+
+-   Simple: `#[builder(start_fn = identifier)]`. Overrides only the name of the "start" method.
+-   Verbose: `#[builder(start_fn(name = identifier, vis = "visibility"))]`.
+    Allows overriding both the name and the visibility of the "start" method.
+    Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility).
+
+**Example:**
+
+::: code-group
+
+```rust [Simple form]
+use bon::builder;
+
+#[builder(start_fn = init)] // [!code highlight]
+struct User {
+    id: u32
+}
+
+User::init() // [!code highlight]
+    .id(42)
+    .build();
+```
+
+```rust [Verbose form]
+use bon::builder;
+
+// `User::init()` method will have `pub(crate)` visibility // [!code highlight]
+// Use `vis = ""` to make it fully private instead         // [!code highlight]
+#[builder(start_fn(name = init, vis = "pub(crate)"))]      // [!code highlight]
+pub struct User {
+    id: u32
+}
+
+User::init() // [!code highlight]
+    .id(42)
+    .build();
+```
+
+:::
+
+### `on`
+
+**Applies to:** <Badge text="structs"/> <Badge text="free functions"/> <Badge text="associated methods"/>
+
+Applies the given builder attributes to all members that match the selected type pattern. For example, you can automatically apply `#[builder(into)]` to all members of type `String` this way:
+
+::: code-group
+
+```rust [Struct]
+use bon::builder;
+
+#[builder(on(String, into))]
+struct Example {
+    id: String,
+    name: String,
+    level: u32,
+}
+
+Example::builder()
+    // `id` and `name` accept `impl Into<String>` because   // [!code highlight]
+    // `on` automatically added `#[builder(into)]` for them // [!code highlight]
+    .id("e-1111")
+    .name("Bon")
+    // `u32` doesn't match the `String` type pattern, // [!code highlight]
+    // so `#[builder(into)]` was not applied to it    // [!code highlight]
+    .level(100)
+    .build();
+```
+
+```rust [Free function]
+use bon::builder;
+
+#[builder(on(String, into))]
+fn example(
+    id: String,
+    name: String,
+    level: u32,
+) {}
+
+example()
+    // `id` and `name` accept `impl Into<String>` because   // [!code highlight]
+    // `on` automatically added `#[builder(into)]` for them // [!code highlight]
+    .id("e-1111")
+    .name("Bon")
+    // `u32` doesn't match the `String` type pattern, // [!code highlight]
+    // so `#[builder(into)]` was not applied to it    // [!code highlight]
+    .level(100)
+    .call();
+```
+
+```rust [Associated method]
+use bon::bon;
+
+struct Example;
+
+#[bon]
+impl Example {
+    #[builder(on(String, into))]
+    fn example(
+        id: String,
+        name: String,
+        level: u32,
+    ) {}
+}
+
+Example::example()
+    // `id` and `name` accept `impl Into<String>` because   // [!code highlight]
+    // `on` automatically added `#[builder(into)]` for them // [!code highlight]
+    .id("e-1111")
+    .name("Bon")
+    // `u32` doesn't match the `String` type pattern, // [!code highlight]
+    // so `#[builder(into)]` was not applied to it    // [!code highlight]
+    .level(100)
+    .call();
+```
+
+:::
+
+This attribute must be of form `on(type_pattern, attributes)`.
+
+- `type_pattern` - type that will be compared with the types of the members. The types are compared textually. For example, `String` doesn't match `std::string::String`. You can use `_` to mark parts of the type to ignore when matching. For example, `Vec<_>` matches `Vec<u32>` or `Vec<String>`. Lifetimes are ignored during matching.
+
+- `attributes` - for now, the only attribute supported in the `attributes` position is [`into`](#into). It sets `#[builder(into)]` for members that match the `type_pattern`.
+
+If you want to apply the `attributes` to all members, you can use the `_` type pattern that matches any type. For example, `#[builder(on(_, into))]`.
+
+For optional members the underlying type is matched ignoring the `Option` wrapper.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+#[builder(on(String, into))]
+struct Example {
+    name: String,
+    description: Option<String>,
+
+    #[builder(default)]
+    alias: String
+}
+
+Example::builder()
+    .name("Bon")
+    // These members also matched the `String` type pattern,
+    // so `#[builder(into)]` was applied to it
+    .description("accepts an `impl Into<String>` here")
+    .alias("builder")
+    .build();
+```
+
+You can specify `on(...)` multiple times.
+
+**Example:**
+
+```rust
+use bon::builder;
+use std::path::PathBuf;
+
+#[builder(on(String, into), on(PathBuf, into))]
+struct Example {
+    name: String,
+    path: PathBuf,
+    level: u32,
+}
+
+Example::builder()
+    .name("accepts `impl Into<String>`")
+    .path("accepts/impl/into/PathBuf")
+    // This member doesn't match neither `String` nor `PathBuf`,
+    // and thus #[builder(into)] was not applied to it
+    .level(100)
+    .build();
+```
+
+
+
+## Member-Level Attributes
+
+### `default`
+
+**Applies to:** <Badge type="warning" text="struct fields"/> <Badge type="warning" text="free function arguments"/> <Badge type="warning" text="associated method arguments"/>
+
+Makes the member optional and assigns a default value to it. There will be two setter methods generated for the member just like for [members of type `Option<T>`](../guide/optional-members). One setter accepts a value of type `T` (type of the member) and the other (with the `maybe_` prefix) accepts an `Option<T>`.
+
+::: tip
+
+Switching between `#[builder(default)]` and `Option<T>` is [compatible](../guide/compatibility#switching-between-optiont-and-builderdefault).
+
+:::
+
+The default value will be lazily computed inside of the [finishing function](#finish_fn) (i.e. `build()` or `call()`). It is computed only if the setter for the member wasn't called or `None` was passed to the `maybe_{member}()` setter.
+
+The default value is computed based on the form of this attribute:
+
+| Form                               | How default value is computed |
+| ---------------------------------- | ----------------------------- |
+| `#[builder(default)]`              | `Default::default()`          |
+| `#[builder(default = expression)]` | `expression`                  |
+
+The result of the `expression` will be converted into the target type using [`Into::into`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html) if [`#[builder(into)]`](#into) is enabled for the setter.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct field]
+use bon::builder;
+
+#[builder]
+struct User {
+    #[builder(default)] // [!code highlight]
+    level: u32,
+
+    // The expression of type `&'static str` is automatically             // [!code highlight]
+    // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight]
+    #[builder(into, default = "anon")]                                    // [!code highlight]
+    name: String,
+
+    // Any complex expression is accepted   // [!code highlight]
+    #[builder(default = bon::vec!["read"])] // [!code highlight]
+    permissions: Vec<String>,
+}
+
+let user = User::builder().build();
+
+assert_eq!(user.level, 0);
+assert_eq!(user.name, "anon");
+assert_eq!(user.permissions, ["read"]);
+```
+
+```rust [Free function argument]
+use bon::builder;
+
+#[builder]
+fn greet_user(
+    #[builder(default)] // [!code highlight]
+    level: u32,
+
+    // The expression of type `&'static str` is automatically             // [!code highlight]
+    // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight]
+    #[builder(into, default = "anon")]                                    // [!code highlight]
+    name: String,
+
+    // Any complex expression is accepted   // [!code highlight]
+    #[builder(default = bon::vec!["read"])] // [!code highlight]
+    permissions: Vec<String>,
+) -> String {
+    format!("Hello {name}! Your level is {level}, permissions: {permissions:?}")
+}
+
+let greeting = greet_user().call();
+
+assert_eq!(greeting, "Hello anon! Your level is 0, permissions: [\"read\"]");
+```
+
+```rust [Associated method argument]
+use bon::bon;
+
+struct User {
+    level: u32,
+    name: String,
+    permissions: Vec<String>,
+}
+
+#[bon]
+impl User {
+    #[builder]
+    fn new(
+        #[builder(default)] // [!code highlight]
+        level: u32,
+
+        // The expression of type `&'static str` is automatically             // [!code highlight]
+        // converted to `String` here via `Into` thanks to `#[builder(into)]. // [!code highlight]
+        #[builder(into, default = "anon")]                                    // [!code highlight]
+        name: String,
+
+        // Any complex expression is accepted   // [!code highlight]
+        #[builder(default = bon::vec!["read"])] // [!code highlight]
+        permissions: Vec<String>,
+    ) -> Self {
+        Self { level, name, permissions }
+    }
+}
+
+let user = User::builder().build();
+
+assert_eq!(user.name, "anon");
+assert_eq!(user.level, 0);
+assert_eq!(user.permissions, ["read"]);
+```
+
+:::
+
+#### Compile errors
+
+This attribute is incompatible with members of `Option` type, since `Option` already implies the default value of `None`.
+
+### `into`
+
+**Applies to:** <Badge type="warning" text="struct fields"/> <Badge type="warning" text="free function arguments"/> <Badge type="warning" text="associated method arguments"/>
+
+Changes the signature of the generated setters to accept [`impl Into<T>`](https://doc.rust-lang.org/stable/std/convert/trait.Into.html), where `T` is the type of the member.
+
+For [optional members](../guide/optional-members), the `maybe_{member}()` setter method will accept an `Option<impl Into<T>>` type instead of just `Option<T>`.
+
+For members that use `#[builder(default = expression)]`, the `expression` will be converted with `Into::into`.
+
+This parameter is often used with the `String` type, which allows you to pass `&str` into the setter without calling `.to_owned()` or `.to_string()` on it.
+
+See the ["Into Conversions In-Depth"](../guide/patterns/into-conversions-in-depth) page that shows the common patterns and antipatterns of `impl Into<T>`.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct field]
+use bon::builder;
+
+#[builder]
+struct Example {
+    #[builder(into)] // [!code highlight]
+    name: String,
+
+    #[builder(into)] // [!code highlight]
+    description: Option<String>,
+
+    // The value passed to `default = ...` is converted with `into` as well // [!code highlight]
+    #[builder(into, default = "anon")]                                      // [!code highlight]
+    group: String
+}
+
+Example::builder()
+    // We can pass `&str` because the setters accept `impl Into<String>`      // [!code highlight]
+    .name("Bon")                                                              // [!code highlight]
+    .description("Awesome crate 🐱. Consider giving it a star on Github ⭐") // [!code highlight]
+    // We can pass `Option<&str>` to `maybe_` methods because they accept     // [!code highlight]
+    // `Option<impl Into<String>>`                                            // [!code highlight]
+    .maybe_group(Some("Favourites"))                                          // [!code highlight]
+    .build();
+```
+
+```rust [Free function argument]
+use bon::builder;
+
+#[builder]
+fn example(
+    #[builder(into)] // [!code highlight]
+    name: String,
+
+    #[builder(into)] // [!code highlight]
+    description: Option<String>,
+
+    // The value passed to `default = ...` is converted with `into` as well // [!code highlight]
+    #[builder(into, default = "anon")]                                      // [!code highlight]
+    group: String
+) {}
+
+example()
+    // We can pass `&str` because the setters accept `impl Into<String>`      // [!code highlight]
+    .name("Bon")                                                              // [!code highlight]
+    .description("Awesome crate 🐱. Consider giving it a star on Github ⭐") // [!code highlight]
+    // We can pass `Option<&str>` to `maybe_` methods because they accept     // [!code highlight]
+    // `Option<impl Into<String>>`                                            // [!code highlight]
+    .maybe_group(Some("Favourites"))                                          // [!code highlight]
+    .call();
+```
+
+```rust [Associated method argument]
+use bon::bon;
+
+struct Example;
+
+#[bon]
+impl Example {
+    #[builder]
+    fn example(
+        #[builder(into)] // [!code highlight]
+        name: String,
+
+        #[builder(into)] // [!code highlight]
+        description: Option<String>,
+
+        // The value passed to `default = ...` is converted with `into` as well // [!code highlight]
+        #[builder(into, default = "anon")]                                      // [!code highlight]
+        group: String
+    ) {}
+}
+
+Example::example()
+    // We can pass `&str` because the setters accept `impl Into<String>`      // [!code highlight]
+    .name("Bon")                                                              // [!code highlight]
+    .description("Awesome crate 🐱. Consider giving it a star on Github ⭐") // [!code highlight]
+    // We can pass `Option<&str>` to `maybe_` methods because they accept     // [!code highlight]
+    // `Option<impl Into<String>>`                                            // [!code highlight]
+    .maybe_group(Some("Favourites"))                                          // [!code highlight]
+    .call();
+```
+
+:::
+
+### `name`
+
+**Applies to:** <Badge type="warning" text="struct fields"/> <Badge type="warning" text="free function arguments"/> <Badge type="warning" text="associated method arguments"/>
+
+Overrdies the name for the setters generated for the member. This is most useful when `#[builder]` is placed on a struct where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions.
+
+**Example:**
+
+::: code-group
+
+```rust [Struct field]
+use bon::builder;
+
+#[builder]
+struct Player {
+    #[builder(name = rank)] // [!code highlight]
+    level: u32
+}
+
+Player::builder()
+    .rank(10) // [!code highlight]
+    .build();
+```
+
+```rust [Free function argument]
+use bon::builder;
+
+#[builder]
+fn player(
+    #[builder(name = rank)] // [!code highlight]
+    level: u32
+) {}
+
+player()
+    .rank(10) // [!code highlight]
+    .call();
+```
+
+```rust [Associated method argument]
+use bon::bon;
+
+struct Player {
+    level: u32,
+}
+
+#[bon]
+impl Player {
+    #[builder]
+    fn new(
+        #[builder(name = rank)] // [!code highlight]
+        level: u32
+    ) -> Self {
+        Self { level }
+    }
+}
+
+Player::builder()
+    .rank(10) // [!code highlight]
+    .build();
+```
+
+:::
+
+This can be used to give a name for the function arguments that use destructuring patterns,
+although it's simpler to just destructure inside of the function body, which should be preferred over using this attribute.
+
+**Example:**
+
+::: code-group
+
+```rust [Preferred destructuring in function body]
+use bon::builder;
+
+#[builder]
+fn example(point: (u32, u32)) {
+    let (x, y) = point;
+}
+
+example()
+    .point((1, 2))
+    .call();
+```
+
+```rust [Discouraged name attribute in destructuring]
+use bon::builder;
+
+#[builder]
+fn example(
+    #[builder(name = point)]
+    (x, y): (u32, u32)
+) {}
+
+example()
+    .point((1, 2))
+    .call();
+```
+
+:::
+
+### `skip`
+
+**Applies to:** <Badge type="warning" text="struct fields"/> <Badge type="warning" text="free function arguments"/> <Badge type="warning" text="associated method arguments"/>
+
+Skips generating setters for the member. This hides the member from the generated builder API, so the caller can't set its value.
+
+The value for the member will be computed based on the form of the attribute:
+
+| Form                            | How value for the member is computed |
+| ------------------------------- | ------------------------------------ |
+| `#[builder(skip)]`              | `Default::default()`                 |
+| `#[builder(skip = expression)]` | `expression`                         |
+
+**Example:**
+
+::: code-group
+
+```rust [Struct field]
+use bon::builder;
+
+#[builder]
+struct User {
+    #[builder(skip)] // [!code highlight]
+    level: u32,
+
+    // Any complex expression is accepted // [!code highlight]
+    #[builder(skip = "anon".to_owned())]  // [!code highlight]
+    name: String,
+}
+
+let user = User::builder()
+    // There are no `level`, and `name` setters generated // [!code highlight]
+    .build();
+
+assert_eq!(user.level, 0);
+assert_eq!(user.name, "anon");
+```
+
+```rust [Free function argument]
+use bon::builder;
+
+#[builder]
+fn greet_user(
+    #[builder(skip)] // [!code highlight]
+    level: u32,
+
+    // Any complex expression is accepted // [!code highlight]
+    #[builder(skip = "anon".to_owned())]  // [!code highlight]
+    name: String,
+) -> String {
+    format!("Hello {name}! Your level is {level}")
+}
+
+let greeting = greet_user()
+    // There are no `level`, and `name` setters generated // [!code highlight]
+    .call();
+
+assert_eq!(greeting, "Hello anon! Your level is 0");
+```
+
+```rust [Associated method argument]
+use bon::bon;
+
+struct User {
+    level: u32,
+    name: String,
+}
+
+#[bon]
+impl User {
+    #[builder]
+    fn new(
+        #[builder(skip)] // [!code highlight]
+        level: u32,
+
+        // Any complex expression is accepted // [!code highlight]
+        #[builder(skip = "anon".to_owned())]  // [!code highlight]
+        name: String,
+    ) -> Self {
+        Self { level, name }
+    }
+}
+
+let user = User::builder()
+    // There are no `level`, and `name` setters generated // [!code highlight]
+    .build();
+
+assert_eq!(user.level, 0);
+assert_eq!(user.name, "anon");
+```
+
+:::
+
+*[Member]: Struct field or a function argument
+*[member]: Struct field or a function argument
+*[members]: Struct fields or function arguments
diff --git a/website/tsconfig.json b/website/tsconfig.json
new file mode 100644
index 00000000..36af3add
--- /dev/null
+++ b/website/tsconfig.json
@@ -0,0 +1,8 @@
+{
+  "include": [
+    "**/*.ts",
+    "**/*.vue",
+    ".vitepress/**/*.ts",
+    ".vitepress/**/*.vue",
+  ]
+}
diff --git a/website/v1/config.mts b/website/v1/config.mts
new file mode 100644
index 00000000..ee7dbb75
--- /dev/null
+++ b/website/v1/config.mts
@@ -0,0 +1,112 @@
+import { DefaultTheme } from "vitepress";
+
+export const sidebars = {
+    "/v1/guide": [
+        {
+            text: "Guide",
+            items: [
+                {
+                    text: "Overview",
+                    link: "/v1/guide/overview",
+                },
+                {
+                    text: "Optional members",
+                    link: "/v1/guide/optional-members",
+                },
+                {
+                    text: "Compatibility",
+                    link: "/v1/guide/compatibility",
+                },
+                {
+                    text: "Into conversions",
+                    link: "/v1/guide/into-conversions",
+                },
+                {
+                    text: "Documenting",
+                    link: "/v1/guide/documenting",
+                },
+                {
+                    text: "Limitations",
+                    link: "/v1/guide/limitations",
+                },
+                {
+                    text: "Benchmarks",
+                    link: "/v1/guide/benchmarks",
+                },
+                {
+                    text: "Alternatives",
+                    link: "/v1/guide/alternatives",
+                },
+                {
+                    text: "Troubleshooting",
+                    link: "/v1/guide/troubleshooting",
+                },
+            ],
+        },
+    ],
+    "/v1/reference": [
+        {
+            text: "Reference",
+            items: [
+                {
+                    text: "#[builder]",
+                    link: "/v1/reference/builder",
+                    items: [
+                        {
+                            text: "Top-level attributes",
+                            link: "/v1/reference/builder#top-level-attributes",
+                            items: [
+                                {
+                                    text: "builder_type",
+                                    link: "/v1/reference/builder#builder-type",
+                                },
+                                {
+                                    text: "expose_positional_fn",
+                                    link: "/v1/reference/builder#expose-positional-fn",
+                                },
+                                {
+                                    text: "finish_fn",
+                                    link: "/v1/reference/builder#finish-fn",
+                                },
+                                {
+                                    text: "start_fn",
+                                    link: "/v1/reference/builder#start-fn",
+                                },
+                            ],
+                        },
+                        {
+                            text: "Member-level attributes",
+                            link: "/v1/reference/builder#member-level-attributes",
+                            items: [
+                                {
+                                    text: "default",
+                                    link: "/v1/reference/builder#default",
+                                },
+                                {
+                                    text: "into",
+                                    link: "/v1/reference/builder#into",
+                                },
+                                {
+                                    text: "name",
+                                    link: "/v1/reference/builder#name",
+                                },
+                                {
+                                    text: "skip",
+                                    link: "/v1/reference/builder#skip",
+                                },
+                            ],
+                        },
+                    ],
+                },
+                {
+                    text: "#[bon]",
+                    link: "/v1/reference/bon",
+                },
+                {
+                    text: "Other items on docs.rs (v1)",
+                    link: "https://docs.rs/bon/1/bon/",
+                },
+            ],
+        },
+    ],
+} satisfies Record<string, DefaultTheme.Sidebar>;
diff --git a/website/docs/guide/alternatives.md b/website/v1/guide/alternatives.md
similarity index 100%
rename from website/docs/guide/alternatives.md
rename to website/v1/guide/alternatives.md
diff --git a/website/v1/guide/benchmarks.md b/website/v1/guide/benchmarks.md
new file mode 100644
index 00000000..ea84de27
--- /dev/null
+++ b/website/v1/guide/benchmarks.md
@@ -0,0 +1,49 @@
+# Benchmarks
+
+`#[builder]` generates code that is easily optimizable by the compiler. This has been tested by the benchmarks below. The benchmarks compare regular positional function call syntax and builder syntax for functions annotated with `#[builder]`.
+
+In many cases `rustc` generates the same assembly code for the builder syntax as it would for a regular function call. Even when generated assembly differs, the performance differences are negligible.
+
+::: tip TIP
+
+Don't take these microbenchmarks for granted. Do your own performance measurements in your application in real conditions. Feel free to [open an issue](https://github.com/elastio/bon/issues) if you find performance problems in `bon`.
+
+:::
+
+## Wallclock statistics
+
+| Benchmark         | Description                                   | Assembly output                                      | Run time
+| --                | --                                            | --                                                   | --
+| `args_3`          | 3 args of primitive types                     | [Equal](https://godbolt.org/z/cc4ao8x6W)             | regular:&nbsp;`6.6536ns`<br/>builder:&nbsp;`6.6494ns`
+| `args_5`          | 5 args of primitive types                     | [Equal](https://godbolt.org/z/M93M3Yfsj)             | regular:&nbsp;`7.9592ns`<br/>builder:&nbsp;`7.9731ns`
+| `args_10`         | 10 args of primitive types                    | [Ordering diff](https://godbolt.org/z/1c9P5Gjrv)     | regular:&nbsp;`18.082ns`<br/>builder:&nbsp;`18.217ns`
+| `args_10_structs` | 10 args of primitive types and structs        | [Equal](https://godbolt.org/z/95vcn78Tn)             | regular:&nbsp;`5.0784ns`<br/>builder:&nbsp;`5.0481ns`
+| `args_10_alloc`   | 10 args of primitive and heap-allocated types | [Instructions diff](https://godbolt.org/z/bzEbqrvPW) | regular:&nbsp;`86.090ns`<br/>builder:&nbsp;`86.790ns`
+| `args_20`         | 20 args of primitive types                    | [Ordering diff](https://godbolt.org/z/GqP44GxnW)     | regular:&nbsp;`37.381ns`<br/>builder:&nbsp;`37.623ns`
+
+## High-precision statistics
+
+| Benchmark         | Instructions count                           | L1&nbsp;accesses                                   | L2&nbsp;accesses                             | RAM&nbsp;accesses
+| --                | --                                           | --                                            | --                                      | --
+| `args_3`          | regular:&nbsp;`106`<br/>builder:&nbsp;`106`  | regular:&nbsp;`136`<br/>builder:&nbsp;`136`   | regular:&nbsp;`2`<br/>builder:&nbsp;`2` | regular:&nbsp;`4`<br/>builder:&nbsp;`4`
+| `args_5`          | regular:&nbsp;`124`<br/>builder:&nbsp;`124`  | regular:&nbsp;`161`<br/>builder:&nbsp;`161`   | regular:&nbsp;`1`<br/>builder:&nbsp;`1` | regular:&nbsp;`9`<br/>builder:&nbsp;`9`
+| `args_10`         | regular:&nbsp;`281`<br/>builder:&nbsp;`281`  | regular:&nbsp;`381`<br/>builder:&nbsp;`380`   | regular:&nbsp;`2`<br/>builder:&nbsp;`2` | regular:&nbsp;`19`<br/>builder:&nbsp;`20`
+| `args_10_structs` | regular:&nbsp;`73`<br/>builder:&nbsp;`73`    | regular:&nbsp;`108`<br/>builder:&nbsp;`108`   | regular:&nbsp;`2`<br/>builder:&nbsp;`2` | regular:&nbsp;`10`<br/>builder:&nbsp;`10`
+| `args_10_alloc`   | regular:&nbsp;`2025`<br/>builder:&nbsp;`2026`| regular:&nbsp;`2823`<br/>builder:&nbsp;`2823` | regular:&nbsp;`3`<br/>builder:&nbsp;`2` | regular:&nbsp;`35`<br/>builder:&nbsp;`35`
+| `args_20`         | regular:&nbsp;`554`<br/>builder:&nbsp;`554`  | regular:&nbsp;`768`<br/>builder:&nbsp;`769`   | regular:&nbsp;`3`<br/>builder:&nbsp;`3` | regular:&nbsp;`34`<br/>builder:&nbsp;`33`
+
+## Conditions
+
+The code was compiled with `opt-level = 3` and `debug = 0`.
+
+### Hardware
+
+The benchmarks were run on a dedicated root server `AX51-NVMe` on [Hetzner](https://www.hetzner.com/).
+
+- OS: Ubuntu 22.04.4 (Linux 5.15.0-76-generic)
+- CPU: AMD Ryzen 7 3700X 8-Core Processor (x86_64)
+- RAM: 62.8 GiB
+
+## References
+
+The source code of the benchmarks is [available here](https://github.com/elastio/bon/tree/master/benchmarks).
diff --git a/website/docs/guide/compatibility.md b/website/v1/guide/compatibility.md
similarity index 100%
rename from website/docs/guide/compatibility.md
rename to website/v1/guide/compatibility.md
diff --git a/website/v1/guide/documenting.md b/website/v1/guide/documenting.md
new file mode 100644
index 00000000..bc90fba0
--- /dev/null
+++ b/website/v1/guide/documenting.md
@@ -0,0 +1,34 @@
+# Documenting
+
+In regular Rust, it's not possible to place doc comments on function arguments. But with `#[builder]` it is. Documentation written on the arguments will be placed on the generated setter methods.
+
+**Example:**
+
+```rust
+use bon::builder;
+
+/// Function that returns a greeting special-tailored for a given person
+#[builder]
+fn greet(
+    /// Name of the person to greet.
+    ///
+    /// **Example:**
+    /// ```
+    /// greet().name("John");
+    /// ```
+    name: &str,
+
+    /// Age expressed in full years passed since the birth date.
+    age: u32
+) -> String {
+    format!("Hello {name} with age {age}!")
+}
+```
+
+::: details How does this work? 🤔
+
+This works because Rust compiler checks for invalid placement of `#[doc = ...]` attributes only after the macro expansion stage. `#[builder]` makes sure to remove the docs from the function's arguments in the expanded code, and instead moves them to the docs on setter methods.
+
+:::
+
+When `#[builder]` is placed on top of a struct, then documentation on the struct fields will be copied to the docs on the setter methods.
diff --git a/website/docs/guide/into-conversions.md b/website/v1/guide/into-conversions.md
similarity index 100%
rename from website/docs/guide/into-conversions.md
rename to website/v1/guide/into-conversions.md
diff --git a/website/docs/guide/limitations.md b/website/v1/guide/limitations.md
similarity index 100%
rename from website/docs/guide/limitations.md
rename to website/v1/guide/limitations.md
diff --git a/website/docs/guide/optional-members.md b/website/v1/guide/optional-members.md
similarity index 100%
rename from website/docs/guide/optional-members.md
rename to website/v1/guide/optional-members.md
diff --git a/website/docs/guide/overview.md b/website/v1/guide/overview.md
similarity index 97%
rename from website/docs/guide/overview.md
rename to website/v1/guide/overview.md
index 8a02870b..b63271f4 100644
--- a/website/docs/guide/overview.md
+++ b/website/v1/guide/overview.md
@@ -1,17 +1,12 @@
-<script setup>
-import { data as version } from '/data/version.data'
-const [_, versionWildcard] = version.match(/(\d+.\d+).\d+/);
-</script>
-
 # Overview
 
 `bon` is a Rust crate for generating compile-time-checked builders for functions and structs.
 
 Add this to your `Cargo.toml` to use it:
 
-```toml-vue
+```toml
 [dependencies]
-bon = "{{ versionWildcard }}"
+bon = "1.2"
 ```
 
 ::: tip
diff --git a/website/v1/guide/troubleshooting.md b/website/v1/guide/troubleshooting.md
new file mode 100644
index 00000000..9f591922
--- /dev/null
+++ b/website/v1/guide/troubleshooting.md
@@ -0,0 +1,23 @@
+# Troubleshooting
+
+If you encounter any issues, then try the following.
+
+## Check for known limitations
+
+Check if your problem is already described on the [known limitations page](./limitations), where you'll also find some suggested workarounds.
+
+## Expand the macro
+
+Suppose you see a compile error coming from the code generated by a macro. The error message is not very informative because it points to some code inside of the macro that you don't see.
+
+In this case, try expanding the macro using Rust Analyzer's command ["Expand macro recursively"](https://rust-analyzer.github.io/manual.html#expand-macro-recursively). It works for any kind of macro including attribute macros.
+
+Then copy the expanded code, delete the original macro invocation, and replace it with the expanded code. Compile your program again to see where exactly the error occurs.
+
+**Example:**
+
+<video controls src="/expand-macro-recursively.mp4" title="Title"></video>
+
+## Open an issue
+
+If nothing else helps, feel free to [open an issue](https://github.com/elastio/bon/issues). We'll definitely figure this out 🐱.
diff --git a/website/v1/reference/bon.md b/website/v1/reference/bon.md
new file mode 100644
index 00000000..1b24b699
--- /dev/null
+++ b/website/v1/reference/bon.md
@@ -0,0 +1,10 @@
+# `#[bon]`
+
+This is a companion macro for [`builder`]. You should place it on top of the `impl` block
+where you want to define methods with the [`builder`] macro. It provides
+the necessary context to the [`builder`] macros on top of the functions
+inside of the `impl` block. You'll get compile errors without that context.
+
+For the examples of the usage of this macro and the reason why it's needed see this paragraph in [the overview](../guide/overview#builder-for-an-associated-method).
+
+[`builder`]: ./builder
diff --git a/website/docs/reference/builder.md b/website/v1/reference/builder.md
similarity index 100%
rename from website/docs/reference/builder.md
rename to website/v1/reference/builder.md


Test patch:
diff --git a/bon/tests/integration/builder/attr_default.rs b/bon/tests/integration/builder/attr_default.rs
index 4c673a46..66b3ead5 100644
--- a/bon/tests/integration/builder/attr_default.rs
+++ b/bon/tests/integration/builder/attr_default.rs
@@ -5,22 +5,28 @@ use crate::prelude::*;
 fn fn_alloc() {
     #[builder]
     fn sut(
-        #[builder(default = "default")] arg1: String,
+        #[builder(default = "default".to_owned())] arg1: String,
         #[builder(default = vec![42])] arg2: Vec<u32>,
-    ) -> (String, Vec<u32>) {
-        (arg1, arg2)
+        #[builder(default = "foo", into)] arg3: String,
+    ) -> (String, Vec<u32>, String) {
+        (arg1, arg2, arg3)
     }
 
-    assert_debug_eq(sut().call(), expect![[r#"("default", [42])"#]]);
+    assert_debug_eq(sut().call(), expect![[r#"("default", [42], "foo")"#]]);
 
     assert_debug_eq(
         sut().arg1("arg1".to_owned()).call(),
-        expect![[r#"("arg1", [42])"#]],
+        expect![[r#"("arg1", [42], "foo")"#]],
+    );
+
+    assert_debug_eq(
+        sut().maybe_arg1(None).call(),
+        expect![[r#"("default", [42], "foo")"#]],
     );
 
     assert_debug_eq(
-        sut().maybe_arg1(None::<String>).call(),
-        expect![[r#"("default", [42])"#]],
+        sut().maybe_arg3(None::<String>).call(),
+        expect![[r#"("default", [42], "foo")"#]],
     );
 }
 
@@ -33,26 +39,29 @@ fn struct_alloc() {
     #[builder]
     #[derive(Debug)]
     struct Sut {
-        #[builder(default = "default")]
+        #[builder(default = "default".to_owned())]
         arg1: String,
 
         #[builder(default = vec![42])]
         arg2: Vec<u32>,
+
+        #[builder(default = "foo", into)]
+        arg3: String,
     }
 
     assert_debug_eq(
         Sut::builder().build(),
-        expect![[r#"Sut { arg1: "default", arg2: [42] }"#]],
+        expect![[r#"Sut { arg1: "default", arg2: [42], arg3: "foo" }"#]],
     );
 
     assert_debug_eq(
         Sut::builder().arg1("arg1".to_owned()).build(),
-        expect![[r#"Sut { arg1: "arg1", arg2: [42] }"#]],
+        expect![[r#"Sut { arg1: "arg1", arg2: [42], arg3: "foo" }"#]],
     );
 
     assert_debug_eq(
         Sut::builder().maybe_arg1(None::<String>).build(),
-        expect![[r#"Sut { arg1: "default", arg2: [42] }"#]],
+        expect![[r#"Sut { arg1: "default", arg2: [42], arg3: "foo" }"#]],
     );
 
     #[bon]
@@ -60,12 +69,14 @@ fn struct_alloc() {
         #[builder]
         fn assoc(
             self,
-            #[builder(default = "default")] arg1: String,
+            #[builder(default = "default".to_owned())] arg1: String,
             #[builder(default = vec![43])] arg2: Vec<u32>,
+            #[builder(default = "foo", into)] arg3: String,
         ) -> Self {
             Self {
                 arg1: format!("{}+{arg1}", self.arg1),
                 arg2: self.arg2.into_iter().chain(arg2).collect(),
+                arg3: format!("{}+{arg3}", self.arg3),
             }
         }
     }
@@ -74,17 +85,54 @@ fn struct_alloc() {
 
     assert_debug_eq(
         sut().assoc().call(),
-        expect![[r#"Sut { arg1: "default+default", arg2: [42, 43] }"#]],
+        expect![[r#"
+            Sut {
+                arg1: "default+default",
+                arg2: [
+                    42,
+                    43,
+                ],
+                arg3: "foo+foo",
+            }"#]],
     );
 
     assert_debug_eq(
         sut().assoc().arg1("arg1".to_owned()).call(),
-        expect![[r#"Sut { arg1: "default+arg1", arg2: [42, 43] }"#]],
+        expect![[r#"
+            Sut {
+                arg1: "default+arg1",
+                arg2: [
+                    42,
+                    43,
+                ],
+                arg3: "foo+foo",
+            }"#]],
+    );
+
+    assert_debug_eq(
+        sut().assoc().maybe_arg1(None).call(),
+        expect![[r#"
+            Sut {
+                arg1: "default+default",
+                arg2: [
+                    42,
+                    43,
+                ],
+                arg3: "foo+foo",
+            }"#]],
     );
 
     assert_debug_eq(
-        sut().assoc().maybe_arg1(None::<String>).call(),
-        expect![[r#"Sut { arg1: "default+default", arg2: [42, 43] }"#]],
+        sut().assoc().maybe_arg3(None::<String>).call(),
+        expect![[r#"
+            Sut {
+                arg1: "default+default",
+                arg2: [
+                    42,
+                    43,
+                ],
+                arg3: "foo+foo",
+            }"#]],
     );
 }
 
diff --git a/bon/tests/integration/builder/attr_into.rs b/bon/tests/integration/builder/attr_into.rs
index bdc2ef59..9d7b4b98 100644
--- a/bon/tests/integration/builder/attr_into.rs
+++ b/bon/tests/integration/builder/attr_into.rs
@@ -7,16 +7,13 @@ fn into_attr_alloc() {
     #[builder]
     fn sut(
         #[builder(into)] set: Option<BTreeSet<u32>>,
-        #[builder(into = false)] disabled_into: String,
+        no_into: String,
     ) -> (Option<BTreeSet<u32>>, String) {
-        (set, disabled_into)
+        (set, no_into)
     }
 
     assert_debug_eq(
-        sut()
-            .set([32, 43])
-            .disabled_into("disabled".to_owned())
-            .call(),
+        sut().set([32, 43]).no_into("disabled".into()).call(),
         expect![[r#"(Some({32, 43}), "disabled")"#]],
     );
 }
@@ -54,7 +51,7 @@ fn into_attr_no_std() {
 #[cfg(feature = "alloc")]
 #[test]
 fn into_string() {
-    #[builder]
+    #[builder(on(String, into))]
     fn sut(arg1: String, arg2: Option<String>) -> (String, Option<String>) {
         (arg1, arg2)
     }
diff --git a/bon/tests/integration/builder/attr_on.rs b/bon/tests/integration/builder/attr_on.rs
new file mode 100644
index 00000000..bd619d00
--- /dev/null
+++ b/bon/tests/integration/builder/attr_on.rs
@@ -0,0 +1,72 @@
+use crate::prelude::*;
+
+struct IntoBool(bool);
+
+impl From<IntoBool> for bool {
+    fn from(value: IntoBool) -> Self {
+        value.0
+    }
+}
+
+struct IntoUnit;
+
+impl From<IntoUnit> for () {
+    fn from(IntoUnit: IntoUnit) {}
+}
+
+struct IntoStrRef<'a>(&'a str);
+
+impl<'a> From<IntoStrRef<'a>> for &'a str {
+    fn from(value: IntoStrRef<'a>) -> Self {
+        value.0
+    }
+}
+
+struct Generic<T>(T);
+struct IntoGeneric<T>(T);
+
+impl<T> From<IntoGeneric<T>> for Generic<T> {
+    fn from(value: IntoGeneric<T>) -> Self {
+        Generic(value.0)
+    }
+}
+
+#[test]
+fn match_any() {
+    #[builder(on(_, into))]
+    fn sut<T>(_arg1: bool, _arg2: Option<()>, _arg3: T) {}
+
+    sut::<&str>()
+        .arg1(IntoBool(true))
+        .arg2(IntoUnit)
+        .arg3(IntoStrRef("foo"))
+        .call();
+}
+
+#[test]
+fn match_str_ref() {
+    #[builder(on(&str, into))]
+    fn sut(_arg1: bool, _arg2: Option<()>, _arg3: &str) {}
+
+    sut().arg1(true).arg2(()).arg3(IntoStrRef("foo")).call();
+}
+
+#[test]
+fn match_path() {
+    #[builder(on(bool, into))]
+    fn sut<T>(_arg1: bool, _arg2: Option<()>, _arg3: T) {}
+
+    sut::<&str>()
+        .arg1(IntoBool(true))
+        .arg2(())
+        .arg3("foo")
+        .call();
+}
+
+#[test]
+fn match_generic() {
+    #[builder(on(Generic<_>, into))]
+    fn sut<T>(_arg1: bool, _arg2: Option<()>, _arg3: Generic<T>) {}
+
+    sut().arg1(true).arg2(()).arg3(IntoGeneric("foo")).call();
+}
diff --git a/bon/tests/integration/builder/attr_skip.rs b/bon/tests/integration/builder/attr_skip.rs
index c7aa06f8..9f9cfe75 100644
--- a/bon/tests/integration/builder/attr_skip.rs
+++ b/bon/tests/integration/builder/attr_skip.rs
@@ -6,7 +6,7 @@ use expect_test::expect;
 fn fn_alloc() {
     #[builder]
     fn sut(
-        #[builder(skip = "skip")] arg1: String,
+        #[builder(skip = "skip".to_owned())] arg1: String,
         #[builder(skip = vec![42])] arg2: Vec<u32>,
     ) -> (String, Vec<u32>) {
         (arg1, arg2)
@@ -24,7 +24,7 @@ fn struct_alloc() {
     #[builder]
     #[derive(Debug)]
     struct Sut {
-        #[builder(skip = "skip")]
+        #[builder(skip = "skip".to_owned())]
         arg1: String,
 
         #[builder(skip = vec![42])]
@@ -41,7 +41,7 @@ fn struct_alloc() {
         #[builder]
         fn assoc(
             self,
-            #[builder(skip = "assoc")] arg1: String,
+            #[builder(skip = "assoc".to_owned())] arg1: String,
             #[builder(skip = vec![43])] arg2: Vec<u32>,
         ) -> Self {
             Self {
diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs
index 3af4ae59..7cfa711a 100644
--- a/bon/tests/integration/builder/mod.rs
+++ b/bon/tests/integration/builder/mod.rs
@@ -1,6 +1,7 @@
 mod attr_default;
 mod attr_expose_positional_fn;
 mod attr_into;
+mod attr_on;
 mod attr_skip;
 mod generics;
 mod lints;
diff --git a/bon/tests/integration/builder/smoke.rs b/bon/tests/integration/builder/smoke.rs
index 9e16454e..b7f47bfc 100644
--- a/bon/tests/integration/builder/smoke.rs
+++ b/bon/tests/integration/builder/smoke.rs
@@ -41,7 +41,7 @@ fn smoke_fn() {
     let actual = sut()
         .arg1(true)
         .arg2("arg2")
-        .arg3("arg3")
+        .arg3("arg3".to_owned())
         .arg4(1)
         .arg7(vec!["arg7".to_owned()])
         .arg8((1, &[true]))
diff --git a/bon/tests/integration/ui/compile_fail/misc.rs b/bon/tests/integration/ui/compile_fail/misc.rs
index a44b3d1a..d0c4d5ca 100644
--- a/bon/tests/integration/ui/compile_fail/misc.rs
+++ b/bon/tests/integration/ui/compile_fail/misc.rs
@@ -11,13 +11,28 @@ fn destructuring((x, y): (u32, u32)) {
 }
 
 #[builder]
-fn explicit_into_equals_true(#[builder(into = true)] _x: u32) {}
+fn unnecessary_into_false(#[builder(into = false)] _x: u32) {}
 
-#[builder]
-fn unnecessary_into_override_true(#[builder(into)] _x: String) {}
+#[builder(on(String, into))]
+fn unnecessary_into(#[builder(into)] _x: String) {}
 
-#[builder]
-fn unnecessary_into_override_false(#[builder(into = false)] _x: u32) {}
+#[builder(on(&dyn std::fmt::Debug, into))]
+fn invalid_type_pattern() {}
+
+#[builder(on(fn(#[attr] a: u32), into))]
+fn attrs_in_on_type_pattern() {}
+
+#[builder(on)]
+fn incomplete_on() {}
+
+#[builder(on())]
+fn incomplete_on2() {}
+
+#[builder(on(_))]
+fn incomplete_on3() {}
+
+#[builder(on(_, ))]
+fn incomplete_on4() {}
 
 #[builder(start_fn())]
 struct EmptyStartFn {}
diff --git a/bon/tests/integration/ui/compile_fail/misc.stderr b/bon/tests/integration/ui/compile_fail/misc.stderr
index bb8e9ffb..c116a7ae 100644
--- a/bon/tests/integration/ui/compile_fail/misc.stderr
+++ b/bon/tests/integration/ui/compile_fail/misc.stderr
@@ -10,68 +10,104 @@ error: can't infer the name to use for this function argument; please use a simp
 8 | fn destructuring((x, y): (u32, u32)) {
   |                          ^^^^^^^^^^
 
-error: No need to write `= true`. Just mentioning the attribute is enough to set it to `true`, so remove the `= true` part.
-  --> tests/integration/ui/compile_fail/misc.rs:14:47
+error: Unexpected type `bool`
+  --> tests/integration/ui/compile_fail/misc.rs:14:44
    |
-14 | fn explicit_into_equals_true(#[builder(into = true)] _x: u32) {}
-   |                                               ^^^^
+14 | fn unnecessary_into_false(#[builder(into = false)] _x: u32) {}
+   |                                            ^^^^^
 
-error: This attribute is redundant and can be removed. By default the the type of this function argument already qualifies for `impl Into`.
-  --> tests/integration/ui/compile_fail/misc.rs:17:45
+error: this `#[builder(into)]` attribute is redundant, because `into` is already implied for this member via the `#[builder(on(...))]` at the top of the function
+  --> tests/integration/ui/compile_fail/misc.rs:17:31
    |
-17 | fn unnecessary_into_override_true(#[builder(into)] _x: String) {}
-   |                                             ^^^^
+17 | fn unnecessary_into(#[builder(into)] _x: String) {}
+   |                               ^^^^
 
-error: This attribute is redundant and can be removed. By default the the type of this function argument already doesn't qualify for `impl Into`.
-  --> tests/integration/ui/compile_fail/misc.rs:20:53
+error: This syntax is not supported in type patterns yet. If you have a use case for this, please open an issue at https://github.com/elastio/bon/issues.
+  --> tests/integration/ui/compile_fail/misc.rs:19:15
    |
-20 | fn unnecessary_into_override_false(#[builder(into = false)] _x: u32) {}
-   |                                                     ^^^^^
+19 | #[builder(on(&dyn std::fmt::Debug, into))]
+   |               ^^^
+
+error: nested attributes are not allowed in the type pattern of #[builder(on(type_pattern, ...))]
+  --> tests/integration/ui/compile_fail/misc.rs:22:17
+   |
+22 | #[builder(on(fn(#[attr] a: u32), into))]
+   |                 ^
+
+error: Expected an attribute of form `on(type_pattern, ...)`
+  --> tests/integration/ui/compile_fail/misc.rs:25:11
+   |
+25 | #[builder(on)]
+   |           ^^
+
+error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime
+  --> tests/integration/ui/compile_fail/misc.rs:28:1
+   |
+28 | #[builder(on())]
+   | ^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: expected `,`
+  --> tests/integration/ui/compile_fail/misc.rs:31:1
+   |
+31 | #[builder(on(_))]
+   | ^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: this #[builder(on(type_pattern, ...))] contains no options to override the default behavior for the selected setters like `into`, so it does nothing
+  --> tests/integration/ui/compile_fail/misc.rs:34:1
+   |
+34 | #[builder(on(_, ))]
+   | ^^^^^^^^^^^^^^^^^^^
+   |
+   = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: expected at least one parameter in parentheses
-  --> tests/integration/ui/compile_fail/misc.rs:22:11
+  --> tests/integration/ui/compile_fail/misc.rs:37:11
    |
-22 | #[builder(start_fn())]
+37 | #[builder(start_fn())]
    |           ^^^^^^^^
 
 error: `skip` attribute can't be specified with other attributes like `into` because there will be no setter generated for this member to configure
-  --> tests/integration/ui/compile_fail/misc.rs:27:21
+  --> tests/integration/ui/compile_fail/misc.rs:42:21
    |
-27 |     #[builder(skip, into)]
+42 |     #[builder(skip, into)]
    |                     ^^^^
 
 error: `skip` attribute can't be specified with other attributes like `name` because there will be no setter generated for this member to configure
-  --> tests/integration/ui/compile_fail/misc.rs:33:28
+  --> tests/integration/ui/compile_fail/misc.rs:48:28
    |
-33 |     #[builder(skip, name = bar)]
+48 |     #[builder(skip, name = bar)]
    |                            ^^^
 
 error: `skip` attribute can't be specified with other attributes like `default` because there will be no setter generated for this member to configure. If you wanted to specify a value for the member, then use the following syntax instead `#[builder(skip = value)]`
-  --> tests/integration/ui/compile_fail/misc.rs:39:21
+  --> tests/integration/ui/compile_fail/misc.rs:54:21
    |
-39 |     #[builder(skip, default = 42)]
+54 |     #[builder(skip, default = 42)]
    |                     ^^^^^^^
 
 error: duplicate key in the map
-  --> tests/integration/ui/compile_fail/misc.rs:45:9
+  --> tests/integration/ui/compile_fail/misc.rs:60:9
    |
-45 |         "Hello": "Blackjack",
+60 |         "Hello": "Blackjack",
    |         ^^^^^^^
 
 error: duplicate key in the map
-  --> tests/integration/ui/compile_fail/misc.rs:46:9
+  --> tests/integration/ui/compile_fail/misc.rs:61:9
    |
-46 |         "Hello": "Littlepip",
+61 |         "Hello": "Littlepip",
    |         ^^^^^^^
 
 error: duplicate value in the set
-  --> tests/integration/ui/compile_fail/misc.rs:49:43
+  --> tests/integration/ui/compile_fail/misc.rs:64:43
    |
-49 |     let set: BTreeSet<String> = bon::set!["mintals", "guns", "mintals", "roses"];
+64 |     let set: BTreeSet<String> = bon::set!["mintals", "guns", "mintals", "roses"];
    |                                           ^^^^^^^^^
 
 error: duplicate value in the set
-  --> tests/integration/ui/compile_fail/misc.rs:49:62
+  --> tests/integration/ui/compile_fail/misc.rs:64:62
    |
-49 |     let set: BTreeSet<String> = bon::set!["mintals", "guns", "mintals", "roses"];
+64 |     let set: BTreeSet<String> = bon::set!["mintals", "guns", "mintals", "roses"];
    |                                                              ^^^^^^^^^
diff --git a/e2e-tests/Cargo.toml b/e2e-tests/Cargo.toml
index b0f8b5c0..d4b1445f 100644
--- a/e2e-tests/Cargo.toml
+++ b/e2e-tests/Cargo.toml
@@ -21,6 +21,7 @@ workspace = true
 bon = { path = "../bon" }
 
 [dev-dependencies]
+anyhow        = "1.0"
 buildstructor = "0.5"
 
 [build-dependencies]
diff --git a/e2e-tests/build.rs b/e2e-tests/build.rs
index d6b086d0..69b100f9 100644
--- a/e2e-tests/build.rs
+++ b/e2e-tests/build.rs
@@ -20,7 +20,9 @@ fn main() {
             })
         })
         .filter(|entry| {
-            entry.file_type().is_file() && entry.path().extension() == Some("md".as_ref())
+            entry.file_type().is_file()
+                && entry.path().extension() == Some("md".as_ref())
+                && !entry.path().starts_with("../website/v1")
         })
         .map(DirEntry::into_path)
         .sorted_unstable()
diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs
index 9bc0996a..c42318b8 100644
--- a/e2e-tests/src/lib.rs
+++ b/e2e-tests/src/lib.rs
@@ -72,7 +72,7 @@ pub fn documented(
 }
 
 /// Function that returns a greeting special-tailored for a given person
-#[builder]
+#[builder(builder_type = Foo)]
 pub fn greet(
     /// Name of the person to greet.
     ///