Skip to Content
ConceptsSignals and the snapshot

Signals and the snapshot

Core signals

Panels report what the user does with track({ type, panel, detail }). The library knows these core types (CoreSignalType):

TypeWhenUseful detail
item_openThe user opened one recorditemKind, itemId, client, label
searchThe user typed a search in a panelquery
filterThe user changed a filterfilter or label
actionThe user did something with a recordactionId, itemId, client
commandThe user typed into the command barquery (the store logs it)
panel_focusThe user clicked or tabbed into a panelvia (the canvas logs it)
panel_dwellThe pointer rested on a paneldurationMs (the canvas logs it)
panel_open, panel_dismissA panel came from or went to the dock
panel_pin, panel_unpinThe user pinned or unpinned a panel
panel_maximize, panel_restore“Make bigger” and “Make smaller”
shortcut, scrollA keyboard shortcut, a list scrollkey
suggestion_accept, suggestion_dismissA suggestion was taken or turned downactionId, label
undoThe user undid a layout change

detail.via says how the user did it: "pointer", "keyboard", "command", or "suggestion". Work from a suggestion or a command does not move the anchor.

The store’s actions (pin, dismiss, open, maximize, acceptSuggestion, runCommand, and others) log their own events, so a panel only tracks what happens inside it.

Your own event types

An app can log types of its own, for example "up_next_open". Say how they count with a SignalProfile:

import { signalProfile } from "@attuneui/core"; export const SIGNAL_PROFILE = signalProfile<SignalType>({ recordOpen: ["up_next_open", "task_start"], work: ["up_next_open", "task_start"], pointer: ["up_next_open", "task_start"], cueOnly: ["setting_change"], });

recordOpen types open one record, work types mean “the user works in this panel”, pointer types are done with the pointer, and cueOnly types are logged but never reach the model. Give the profile and your own sentences (describe) to the store.

Recent use

panelUsage measures how much each panel was used lately. Each event’s weight halves every 90 seconds (USAGE_HALF_LIFE_MS), and the busiest panel is 1. CORE_USAGE_WEIGHTS and eventWeight give the core types their weights: opening a record or an action counts 3, a search or a filter 2.5, a focus 1, a pointer rest by its length.

The snapshot

The model reads the activity in words. buildSnapshot writes the InteractionSnapshot:

  • recent_activity: the last events as sentences, oldest first, at most 15, with repeats collapsed. For example “Opened ticket T-201 in Tickets” or “Searched Articles for “reset"".
  • current_focus: the focused panel, and what it shows.
  • visible_panels: the titles on screen.
  • behavior_observations: facts code measured, in words.

The observations are where code does the counting, because a model cannot count:

  • “The searches keep repeating the same word”
  • A search phrased as a how-to question
  • A record opened again and again without acting on it
  • A panel sent back to the dock right after it opened
  • Switching back and forth between two panels
  • Opening the help panel, turning down suggestions, an undo
  • Keyboard or pointer use, and the pace of the work
  • “Only one action so far”, or no activity for a few minutes

The app gives its words for the sentences (EventWords): the panel titles, what its record kinds are called, and its actions in the past tense.

const WORDS: EventWords<PanelId> = { panels: CATALOG.panels, itemWords: { ticket: "ticket", customer: "customer", article: "help article", macro: "saved reply" }, actionPast: (actionId, d) => (actionId === "reply_ticket" ? `Replied to ticket ${d.itemId}` : "Took an action"), };
Last updated on