The facet-entry trigger character (Spec 024) —
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 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-styleA 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).
Summary
Types
What a host passes as facet_trigger.
Trigger grapheme to the facets it offers — :all for the whole registry, or
an explicit list of facet keys.
Functions
The configured graphemes, sorted — the list the discoverability hint renders.
Normalises a facet_trigger attr into a t/0, or nil for "no trigger
configured" (today's always-on facet suggestions).
The facets grapheme offers, or nil if it isn't a configured trigger.
Types
Functions
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)
[]
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"
@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