Skip to content

Contact Notes

Contact notes are free-text entries attached to a contact record. They support both user-authored notes and AI-authored notes created automatically after each call. Notes render as styled markdown in read mode and raw text in edit mode (GitHub-style).

The notes system uses a three-axis taxonomy (visibility, origin, area) to categorize entries, enabling precise filtering and automatic labeling.

pub struct ContactNote {
pub id: String,
pub content: String,
pub author_name: String, // Derived from area + origin
pub can_edit: bool, // true only for the note's author (user-written notes only)
pub visibility: ContactNoteVisibility,
pub origin: ContactNoteOrigin,
pub area: ContactNoteArea,
pub source_entity_type: Option<ContactNoteSourceEntityType>,
pub source_entity_id: Option<String>,
pub created_at: String,
pub updated_at: String,
}

ContactNoteVisibility — who can see the note:

  • UserVisible — displayed in the UI notes list
  • Internal — hidden from UI, used for system metadata

ContactNoteOrigin — how the note was created:

  • Manual — user-written via UI
  • Agent — generated by AI agent during call
  • Workflow — created by automation
  • System — platform-generated metadata

ContactNoteArea — what domain the note belongs to:

  • General — default user notes
  • Memory — synthesized contact memory (always pinned to top)
  • CallAnnotation — AI summary of a call
  • SmsAnnotation — AI summary of SMS interaction
  • AiEnrichment — AI-extracted contact facts
  • Alert — system notifications

The author_name is derived from area and origin:

AreaOriginAuthor Label
Memory*Contact Memory
CallAnnotation*Call Assistant
SmsAnnotation*SMS Agent
AiEnrichment*AI Enrichment
Alert*System Alert
GeneralManualUser’s name
GeneralAgentAI Agent
GeneralWorkflowAutomation
GeneralSystemSystem

Memory notes (area: Memory) are always pinned to the top using ORDER BY (area = 'memory'), updated_at DESC, created_at DESC.

Note content is rendered as styled markdown in read mode using pulldown-cmark. The Markdown component uses dangerous_inner_html to render HTML output with .prose CSS rules for headings, lists, and paragraphs.

Edit mode always shows raw text — the same markdown that the AI or user wrote. This matches the GitHub editing experience.

#[component]
pub fn Markdown(
content: String,
#[props(default)] class: String,
#[props(default)] highlight_query: Option<String>,
) -> Element

The class prop appends additional CSS classes to the .prose wrapper. Use prose-sm for compact contexts like timeline previews and messaging feed cards. The highlight_query prop wraps matching text in <mark> tags for search result highlighting.

The component is used across the app wherever markdown content appears:

ContextComponentClass
Contact notes (read mode)ContactNoteCard(default)
Call details — transcriptionTranscriptionCard(default)
Call details — analysisAnalysisCard(default)
Contact timeline — call entriesTimelineCallEntry(default)
Contact timeline — note previewsTimelineNoteEntryprose-sm
Messaging — call cardsMessagingCallEntryprose-sm

AI-generated interaction notes use structured sections:

## Summary
Short description of the call.
## Key Points
- Specific points discussed
## Next Steps
- Any follow-up items (omitted if none)

All endpoints require an authenticated session. Only notes with visibility: UserVisible are returned by the API — internal notes are excluded.

MethodRouteDescription
GET/api/contacts/{contact_id}/notesList user-visible notes, memory note pinned first, then newest first
POST/api/contacts/{contact_id}/notesAdd a note (content: String)
PUT/api/contacts/{contact_id}/notes/{note_id}Update note content (author only)
DELETE/api/contacts/{contact_id}/notes/{note_id}Delete a note (author only)

The GET endpoint filters to visibility: UserVisible OR NULL (legacy records default to UserVisible). Notes are sorted with Memory notes first (by updated_at DESC), followed by all other notes (by created_at DESC).

Only the user who created a note can edit or delete it (can_edit: true). Attempting to modify another user’s note returns 403 Forbidden. Agent-authored notes (origin: Agent | Workflow | System) cannot be edited or deleted via the API — can_edit is always false for them.

Displays a note with its label (derived from area and origin), creation timestamp, and an “(edited)” badge if the content was modified. In read mode the content is rendered as markdown. In edit mode a plain textarea with the raw markdown is shown.

Each note shows a badge derived from source_badge_label():

  • General/Manual → “Manual”
  • General/Agent → “AI”
  • General/Workflow → “Workflow”
  • CallAnnotation → “Call”
  • SmsAnnotation → “SMS”
  • AiEnrichment → “AI”
  • Memory → No badge (always pinned, label is author name)

The notes list in the contact detail view includes a search bar with real-time filtering (300ms debounce):

  • Search — filters notes by content (case-insensitive substring match)
  • Area filter — dropdown to show only notes from a specific area (General, Memory, Call, SMS, AI, Alert)

Filter state is local to the component — cleared when navigating away from the contact.

The Contact Memory note (area: Memory) appears first in the list with a distinct label and no edit/delete controls. All other notes follow in reverse-chronological order.

  • Contact Memory — how the system note is generated and used by agents
  • Contact — parent module overview