Product design · Signals UX

Designing signals

Signals are the moments that make a company worth talking to right now - a raise, a hiring spike, a new CMO - discovered by agents and rendered across seven surfaces in ac-frontend. This page is the design spec for that rendering: the 14-type taxonomy and its color system, the anatomy of a signal card across four densities, the 0-10 lead score scale, the origin tag that marks a pulled signal from a pushed one, and the interaction rules that keep every surface consistent.

Types 14 canonicalDensities card · row · timeline · compactSurfaces 7Origin sonar · proactive
Sonar discoversUnifiedSignalevery surface
00

The mental model

A signal is intelligence, not interaction history. Activities record what your team did to a company; signals record what the world did to it. That distinction drives every design decision below: signals are discovered by agents, attached to entities, and ranked - never typed into a form as a unit of work.

Discovered, not typed

Sonar and Envoy emit signals during workflow runs with source_agent and workflow_run_id stamped on. The UI is a reading experience first; manual creation exists but is the exception.

Attached, not owned

One signal can attach to many companies and people through scored join rows. A funding round is one fact even when five tracked contacts work there - surfaces render attachments, not copies.

Ranked, not chronological-only

The lead score and attachment_score decide what surfaces first. Time orders the timeline; relevance orders the launchpad. The same data, two reading orders.

whyWhy this matters for design

Because signals arrive continuously and unprompted, the UX problem is triage, not entry. Every surface below is an answer to one question: how much signal detail does this moment need - a pill, a row, a card, or a full history?

01

The taxonomy and its colors

14 canonical signal_type values, each mapped to one of six pill variants by getSignalTypeVariant(). The variant drives both the pill and the card background, so color always means the same thing. Pick a type to see exactly what the product renders.

'funding'variant: 'success'bg-green-50 border-green-200
Funding1w ago
Raised $14M Series A

Closed a $14M Series A to fund expansion into the US market.

↗ techcrunch.com

Funding - A raise just landed - budget exists and priorities reset.

LLMCustom types normalize onto this list

Users and agents can emit free-form types; an LLM normalizer (ENG-593) maps them onto the canonical 14 so filtering and color stay coherent. The taxonomy is a rendering contract, not a validation wall.

02

Anatomy of a signal, four densities

One unified shape (UnifiedSignal), one renderer family (SignalCard / CrmSignalItem), four densities. Switch density to see how the same funding signal degrades gracefully; click a numbered part for what it does and why.

Company detail, drawers
Funding 11w ago 2
Raised $14M Series A 3

"The round was led by a growth fund and will fund US expansion." 4

↗ techcrunch.com 5🗑 6

Full content: pill, date, description, snippet, source. The reading density.

03

The lead score scale

Signals say what happened; the lead score says how much it matters. An 11-point scale (0-10) collapsed into five labeled tiers (ENG-741), rendered by LeadScoreBadge next to signals on the launchpad and in Sonar results. Drag the slider.

8
Strong fitlead_score: 8 / 10
9-10Very strong fit
7-8Strong fit
5-6Medium fit
3-4Weak fit
0-2Very weak fit
pairScore and signal travel together

On ranked surfaces the badge sits beside the signal pills and lead_reason explains the number in one line. Fact, weight, and reasoning - never just a number on its own.

04

Where signals surface

Six surfaces, one renderer family, density chosen by intent. Pick a surface to see its layout, the component behind it, and the actions it allows.

component LaunchpadRecentSignalsdensity row, grouped by company
Recent signalsOpen Sonar
Northwind Agency 9
FundingHiring+21w
Aster Studio 7
Leadership2d

The "hottest signals" widget above the fold - a ranked mix of both origins from intel_org_signal_feed. Each row carries a small origin tag so the user can tell a signal they pulled (sonar) from one the system pushed (proactive). Company name links into CRM; an "Open Sonar" button appears when the app is installed.

Navigate to companyOpen SonarHover-preview a pillRead the origin tag
05

Interaction patterns

The behaviors that repeat on every surface - the contract between the signal system and the rest of the product.

Hover to expand

Every type pill wraps in an AcHoverCard. The pill stays small for scanning; hovering reveals the full signal - date, description, source - without navigation. Detail on demand, never detail by default.

Overflow discipline

Row density caps visible pills at 2 and folds the rest into a +N badge whose tooltip lists the hidden type names. A company with 9 signals scans exactly as fast as one with 2.

Confirm-gated delete

Delete follows the destructive-action pattern: confirmation alert, soft delete through the store (deleted_at, never a hard drop), success toast. Caches invalidate on every write.

Freshness without spam

useCrmSignalsStore caches per company with a 30s stale-after TTL and batch-fetches many companies in one request. In-flight fetches cancel via AbortController; no polling, no websockets.

Filters that persist

The data layer tab keeps filter state (type, company, search) in a module-scoped composable and syncs it with saved filters, so a triage view survives navigation and sessions.

Score answers "why now"

Signals explain what happened; the lead score badge next to them says how much it matters. The pair turns a feed of facts into a ranked queue of conversations worth starting.

06

Design rules

The do's and don'ts that keep a new signal surface consistent with the seven that exist.

✓ do

One color system, all surfaces

signal_type → AcPill variant → card background, always through getSignalTypeVariant() and getSignalCardBg(). A funding signal is green on the launchpad, in the timeline and in the table.

✓ do

Density follows intent

Row for scanning, card for reading, timeline for history, compact for context. Pick by what the user is doing on that surface, not by available space.

✓ do

Provenance always visible

Source link, event date and source_agent travel with every signal. A signal without a source reads as a rumor; the UI never renders one without its link when a URL exists.

✓ do

Label the origin

On the launchpad mix, tag each row sonar (the org pulled it) or proactive (an agent pushed it), read from intel_org_signal_feed.origin. The user should never wonder whether they asked for a signal or the system surfaced it.

✓ do

Unify before rendering

Every producer shape (relational, Envoy synthetic, legacy JSONB) adapts to UnifiedSignal before it touches a component. One renderer, many sources.

✕ don't

No new colors per surface

Unmapped types (event, news, product, expansion, partnership) fall back to neutral gray. When a type becomes common, extend the variant map - never hardcode a color in a component.

✕ don't

No raw scores

Lead score renders as the 0-10 badge with one of five labels ("Very strong fit"). Never expose a raw float or invent intermediate tiers.

✕ don't

No writes in AI context

The Envoy panel is read-only. Deleting evidence from a drafted email would silently change what the AI claimed to know.

✕ don't

No detail walls

Never render full signal cards in a scanning surface. If a surface shows more than ~3 signals per entity, switch to pills + hover or a +N fold.

srcWhere this lives in code

Types: src/types/signal.ts · colors and labels: src/helpers/signalHelpers.ts · renderers: SignalCard, CrmSignalItem, SignalTypePill, LeadScoreBadge · state: useCrmSignalsStore · API: /crm/signals. The storage-side view of the same system is documented in the signals tables.

AgencyCore · Product design · Sales signals14 types · 4 densities · 7 surfaces · 0-10 score