Accepted 2026-10-31 by the owner: “Accept both” (asked directly in design-system session c6,
STA-1371). Proposed The owner’s answers that day (design-system session c6, STA-1371): icons in
fields “On focus/hover”; the glow is the “Brand gradient”; on naming, “as tamo as all things
should be tamo branded event the glowing one”, so everything, the glow included, is Tamo’s; wave 1
is “Filter bar ask-in-words, Rich Text Editor AI, Text fields + text area, Data table + command
palette, all of them check if we need more and recommend”. The recommended additions (an Ask Tamo
launcher and side panel, form fill from pasted text, natural-language dates) are built with wave
1.
Context
The products need AI inside ordinary screens, not only in a chat: tidying a note, filtering a table in words, filling a form from an email, picking “next Friday”. Each component was about to grow its own AI button, its own loading state and its own idea of when a result is applied. @stackra/ui must stay free of model calls (it runs on web and native, offline, in every product, AI module installed or not), and an AI write must never land without the person’s say (0021). Figentra’s assistant is Tamo, with its own brand kit (figentra/brand brands/tamo).
Decision
1. AI only where it saves real work
Tamo appears on free text (TextField of typetext, TextArea, the rich text editor), on collections (FilterBar,
DataTable, DataView), in the CommandPalette, on a Form with a schema handle, on the date pickers, and in the Ask Tamo
panel. It never appears on a password, OTP, PIN, money, number, phone, email or URL input, a checkbox, a switch, a slider
or a rating; a form fill only reaches plain text inputs and text areas unless the app lists the fields.
2. AI drafts, the person applies
Every action previews, and nothing changes until the person says so (0021):
One state set serves every part: idle, working (the shimmer and Stop), ready (the preview), error (the message and Try
again), through
useTamoTask and TamoStatusLine.
3. @stackra/ui exposes a slot; @stackra/ai/react fills it
@stackra/ui declares the contribution point and draws the parts; it never calls a model:TamoSlot(./contracts):isEnabled(scope), one task runnerrun(task, input, signal)over the typedTamoTasks(transform,continue,draft,filter,summarise,fill_column,fill_form,date,ask), the contributedactions, the screen’s name and the panel’s state.TamoContextanduseTamo(scope)(the shared React layer, web and native). With no slot (the AI module not installed) no Tamo part draws. Components takeai={false}to opt out where a slot would otherwise show.- The parts:
TamoMark,TamoButton,TamoMenu,TamoGhostText,TamoDraftReview,TamoAskBox,TamoMarker,TamoStatusLine,TamoLauncher,TamoPanel, andTamoCollectionTools, on web and native.
TamoProvider (and useTamoSlot for a host that draws its own context, such as a native app) takes a
TamoClient, the tenant’s settings, the screen and the page context
(0490),
and draws the panel once. A TamoClient is the host’s transport to the AI service, where the prompts, decide() and the
agent runtime run with the model’s credentials; the browser never holds a key. createMockTamoClient() is a
deterministic offline client for the playground and tests. The dependency runs one way: @stackra/ai uses @stackra/ui,
never the reverse.
4. Tamo is the brand of every AI surface
The words are “Ask Tamo” and «اسأل تامو»; the AI mark is Tamo’s tile mark (AiMark now draws TamoMark). Tokens v2
(0501) gain a tamo family, seeded
from the brand kit’s color.json and motion.json (read only; the brand session owns the values): tamo-glow-start,
tamo-glow-mid, tamo-glow-end (the glow gradient), tamo-shimmer, tamo-mark, tamo-mark-eyes, tamo-name, and the
tamo-shimmer motion step. A tenant overrides them from the dashboard like any palette token (white-label); the
defaults are Tamo’s. The shimmer and the mark’s eye scan stop under reduced motion.
5. Whether Tamo is on is configuration, not code
TamoSettings is dashboard data per tenant, per screen and per field, each level lockable; resolveTamoEnabled reads
it: a locked tenant level wins, then a locked screen, then the field’s own setting, then the screen’s, then the
tenant’s. The default is on wherever a slot exists, once the AI module is installed.
6. Icons in fields appear on hover or focus
Inside a text field Tamo’s icon-only button appears on hover or focus (on native, on focus) and stays while a task runs or its menu is open. On the FilterBar, the collection toolbars and the editor’s toolbar it is always shown.7. The rich text editor plugs in later
The editor (0466) lives on its own branch. Its AI is an editor contribution that reads the sameTamoContext: a toolbar and bubble-menu
TamoMenu (always shown), the selection sent to run("transform", …) with the same actions and the slot’s contributed
ones, TamoDraftReview before the result is applied as one undoable transaction, and continue as ghost text at the
caret. No editor code lands in @stackra/ui’s Tamo parts for it.
8. Shortcuts
TamoLauncher takes shortcut (default Mod+J), shown and announced (aria-keyshortcuts), and binds it through the
shortcut registry as the ai.ask action (ADR 0508), so a person can remap it.
Consequences
- Every AI surface looks, sounds and behaves the same, and a product without the AI module ships no AI UI.
- A new AI capability is a new task in
TamoTasks(a contract change) plus the service’s answer; a module’s own text action is a contributedTamoActionItemand needs no change here. - The text fields keep their own value when uncontrolled once Tamo is on, so an accepted draft can land in them.
@stackra/ai/reactimports @stackra/ui’s web entry; a native host builds the slot withuseTamoSlotand puts it in @stackra/ui/native’sTamoContext.
Enforcement
- @stackra/ui’s tests cover each part and each wired component with a stand-in slot; @stackra/ai’s tests cover the resolver, the mock client and the provider.
stackra/no-literal-*lint keeps the parts on tokens; the token tests freeze the tamo family.- Planned: a lint that refuses a Tamo slot on the excluded input kinds.