# `PhoenixKit.Mentions.Token`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.7/lib/phoenix_kit/mentions/token.ex#L1)

The stored form of a mention, and the only thing that turns free text into
a link.

Two shapes, both closed and both carrying their own label:

    @[user:018e3c4a-9f6b-7890-abcd-ef1234567890|Alice Smith]
    #[project:018e3c4a-9f6b-7890-abcd-ef1234567890|Q3 Launch]

## Why the label lives in the text

The token is self-contained on purpose. Text gets copied between records,
exported, kept in edit history, and read by people with JavaScript off —
and a module can be uninstalled entirely. A bare foreign key survives none
of that; this survives all of it, degrading to something a human can still
read. It also keeps full-text search working: searching "Alice" finds the
comment, because "Alice" is literally in the column.

The label is a SNAPSHOT of what the author saw when they picked. It is not
the display title — `PhoenixKit.Mentions` re-resolves that per viewer at
render, and deliberately never shows a refreshed title to someone who
cannot open the record.

## What is NOT a mention

A bare `@alice` or `#launch` is ordinary prose and is never linked. Only
the closed form above counts, which is what makes escaping mostly a
non-problem: a token needs the trigger, a known type, a syntactically valid
UUID, a `|`, a label and a `]`, so typing one by accident is not a thing
that happens.

Two deliberate consequences:

  * Publishing's `#hashtag` feature is untouched — the trigger character is
    shared, the stored form is not.
  * An unfinished token (someone typing, or a truncated paste) stays plain
    text rather than becoming a broken link.

For the rare case of writing a complete-looking token that should NOT
link, prefix it with a backslash: `\@[user:…|Alice]`. `render/2` strips the
backslash and leaves the rest as text.

# `kind`

```elixir
@type kind() :: :user | :resource
```

`:user` for an `@` ping, `:resource` for a `#` record link.

# `t`

```elixir
@type t() :: %PhoenixKit.Mentions.Token{
  kind: kind(),
  label: String.t(),
  raw: String.t(),
  type: String.t(),
  uuid: String.t()
}
```

# `max_label_length`

```elixir
@spec max_label_length() :: pos_integer()
```

Longest label a token may carry.

# `parse`

```elixir
@spec parse(String.t() | nil) :: [t()]
```

Every mention in `text`, in order, ignoring backslash-escaped ones.

Returns `[]` for nil or non-binary input so callers can pipe a possibly
empty field straight in.

# `pattern`

```elixir
@spec pattern() :: Regex.t()
```

The regex that recognises a token. Exposed so consumers can reuse it.

# `split`

```elixir
@spec split(String.t() | nil) :: [String.t() | t()]
```

Splits `text` into a list of plain strings and `t()` structs, in order.

This is what a renderer walks: everything that isn't a mention comes back
as a binary to be escaped and printed as-is, and an escaped token comes
back as text with its backslash removed.

# `to_plain_text`

```elixir
@spec to_plain_text(String.t() | nil) :: String.t()
```

The text with every mention replaced by its label — what a plain-text
channel (an email digest, a search index, a notification preview) should
show instead of raw tokens.

# `to_string`

```elixir
@spec to_string(kind(), String.t(), String.t(), String.t()) ::
  {:ok, String.t()} | :error
```

Builds the stored form. Returns `:error` when the label can't be
represented — the picker should then pick a different label rather than
the caller escaping anything.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
