# `Flicker.Trigger`
[🔗](https://github.com/team-alembic/flicker/blob/v0.1.0/lib/flicker/trigger.ex#L1)

The facet-entry trigger character ([Spec 024](https://github.com/team-alembic/flicker/blob/main/docs/specs/spec-024-facet-trigger-character.md)) —
parsing and validating the `facet_trigger` attr, and answering "does this
token open the facet menu, and for which facets".

Without a trigger, every bare word the user types is classified
`{:key, prefix}` and offers facet-key suggestions. That is right for a
dedicated filter bar and wrong for a search box whose main job is free text.
A trigger gates *discovery* on an explicit gesture: `@` opens the facet menu,
and nothing else does.

## The invariant

**A trigger is input sugar and never grammar.** `Flicker.Query.parse/2` has no
clause for it, no token contains it, no pill displays it, and no URL carries
it. `@stat` completes to `status:` — the trigger disappears because choosing a
suggestion replaces the whole token, not because anything strips it.

This is the same separation
[ADR-013](https://github.com/team-alembic/flicker/blob/main/docs/adrs/adr-013-canonical-tokens-localised-display.md)
draws between canonical tokens and localised display, applied to input
affordances: the affordance is configurable precisely because nothing
persists it.

## Gating discovery, not recognition

A trigger stops Flicker *offering* facet keys unprompted. It does not stop it
understanding one that is already there: `status:active` typed out in full, or
pasted, or restored from a URL, still classifies as a facet value against the
whole registry. If the trigger gated recognition too, a shared search URL
would silently stop filtering.

## Configuration

    facet_trigger="@"                              # one trigger, every facet
    facet_trigger={%{"@" => [:worker], "#" => [:tag]}}  # scoped, Slack-style

A trigger must be a single grapheme, must not be whitespace, and must not be
a character that can legally start a facet key (a letter, digit, `_` or `?`)
— `s` as a trigger would be indistinguishable from the first letter of
`status`. `:` **is** allowed: it is the operator, but the operator only ever
appears *after* a key run, so a token-initial `:` is unambiguous (`:stat` is
a trigger, `status:` is not).

# `config`

```elixir
@type config() :: nil | String.t() | map() | keyword()
```

What a host passes as `facet_trigger`.

# `t`

```elixir
@type t() :: %{required(String.t()) =&gt; :all | [atom()]}
```

Trigger grapheme to the facets it offers — `:all` for the whole registry, or
an explicit list of facet keys.

# `graphemes`

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

The configured graphemes, sorted — the list the discoverability hint renders.

Sorted rather than in configuration order so the hint is stable across
renders regardless of how the host's map happens to enumerate.

## Examples

    iex> Flicker.Trigger.graphemes(%{"@" => :all, "#" => [:tag]})
    ["#", "@"]

    iex> Flicker.Trigger.graphemes(nil)
    []

# `parse!`

```elixir
@spec parse!(config()) :: t() | nil
```

Normalises a `facet_trigger` attr into a `t/0`, or `nil` for "no trigger
configured" (today's always-on facet suggestions).

Raises `ArgumentError` on anything unusable rather than degrading, because a
mistyped trigger silently disabling every facet is a far worse failure than a
crash in dev.

## Examples

    iex> Flicker.Trigger.parse!(nil)
    nil

    iex> Flicker.Trigger.parse!("@")
    %{"@" => :all}

    iex> Flicker.Trigger.parse!(%{"@" => [:worker], "#" => [:tag]})
    %{"#" => [:tag], "@" => [:worker]}

    iex> Flicker.Trigger.parse!(%{})
    nil

    iex> Flicker.Trigger.parse!("@@")
    ** (ArgumentError) facet_trigger must be a single character, got: "@@"

    iex> Flicker.Trigger.parse!("s")
    ** (ArgumentError) facet_trigger cannot be a character that starts a facet key, got: "s"

# `scope`

```elixir
@spec scope(t() | nil, String.t(), [Flicker.Facet.t()]) :: [Flicker.Facet.t()] | nil
```

The facets `grapheme` offers, or `nil` if it isn't a configured trigger.

`nil` is the caller's signal to treat the token as ordinary text rather than
as facet entry — it is deliberately distinct from `[]`, which means "a
configured trigger whose facets are all filtered out".

## Examples

    iex> facets = [Flicker.Facet.new(key: :worker), Flicker.Facet.new(key: :tag)]
    ...> Flicker.Trigger.scope(%{"@" => [:worker]}, "@", facets) |> Enum.map(& &1.key)
    [:worker]

    iex> facets = [Flicker.Facet.new(key: :worker)]
    ...> Flicker.Trigger.scope(%{"@" => :all}, "#", facets)
    nil

---

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