Skip to content

Mentions — the @mention picker over Data Lookup

Granit.Mentions is the @mention picker — the typeahead that pops when a user types @ in a comment, a chat message, or a task description. Its trick is that it adds no new contract: a mention is just an existing lookup source tagged as mentionable. One facade source fans the picker across every tagged type, so @alice (a user), @INV-2043 (an invoice), and @Cardiology (a department) all surface from the same search box.

The module registers a single facade ILookupSource named mentions. When the client queries it, the facade fans the search across every source you tagged with AddMentionSource, merges the hits, and re-stamps each item’s value as a composite type:value so one endpoint can resolve any type. Because it rides on Data Lookup, there is no new endpoint — the picker is served by the existing GET /lookups/mentions.

  • DirectoryGranit.Mentions/ the whole module — GranitMentionsModule, MentionLookup, the facade source

There are no satellite packages: it depends only on Granit.DataLookup and Granit.Authorization.

A mention source must already exist as a lookup source — register it the usual Data Lookup way (AddQueryDefinitionLookup, AddQueryableLookup, or a custom ILookupSource), then tag it:

// 'users' and 'invoices' are lookup sources registered elsewhere
builder.Services.AddMentionSource("users");
builder.Services.AddMentionSource("invoices");

AddMentionSource registers the mentions facade on first use (it is idempotent), so you do not call anything else. Each tag is one line; the entity keeps its own label formatting, localization, and permission from its lookup source.

GET /lookups/mentions?search=ali
GET /lookups/mentions?search=2043&scope.type=invoices
ParameterRole
searchThe typeahead term
scope.typeOptional — narrow the picker to one tagged type (e.g. users)
PageSizeCaps the merged result count

Each returned item carries a composite Value (users:3f2a…), the source’s own Label, and an extra.type field naming the source — so the client can render a per-type icon and, later, resolve the pick back through the same facade.

This is the design’s one sharp edge, and it is deliberate. Each tagged source keeps its own RequiredPermission. When the facade fans a search, a source the caller may not see is silently skipped — not refused. The picker returns what the user is allowed to mention and stays quiet about the rest, instead of failing the whole request with a 403 because one type among five was off-limits.

Resolution (type:value → item) applies the same rule: an unknown type, an unregistered source, or a denied permission yields null, never an error. The composite value means a single resolve call handles every mentionable type.

  • Data Lookup — the lookup-source primitive mentions builds on
  • Agentic Chat — where @mentions feed entity context into an AI turn
  • QueryEngine — the filter pickers that share the same lookup sources
  • Timeline — where @mentions surface inside comments and chatter entries