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.
Data Model
Section titled “Data Model”ContactNote
Section titled “ContactNote”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,}Taxonomy Enums
Section titled “Taxonomy Enums”ContactNoteVisibility — who can see the note:
UserVisible— displayed in the UI notes listInternal— hidden from UI, used for system metadata
ContactNoteOrigin — how the note was created:
Manual— user-written via UIAgent— generated by AI agent during callWorkflow— created by automationSystem— platform-generated metadata
ContactNoteArea — what domain the note belongs to:
General— default user notesMemory— synthesized contact memory (always pinned to top)CallAnnotation— AI summary of a callSmsAnnotation— AI summary of SMS interactionAiEnrichment— AI-extracted contact factsAlert— system notifications
The author_name is derived from area and origin:
| Area | Origin | Author Label |
|---|---|---|
Memory | * | Contact Memory |
CallAnnotation | * | Call Assistant |
SmsAnnotation | * | SMS Agent |
AiEnrichment | * | AI Enrichment |
Alert | * | System Alert |
General | Manual | User’s name |
General | Agent | AI Agent |
General | Workflow | Automation |
General | System | System |
Memory notes (area: Memory) are always pinned to the top using ORDER BY (area = 'memory'), updated_at DESC, created_at DESC.
Markdown Rendering
Section titled “Markdown Rendering”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.
Markdown Component
Section titled “Markdown Component”#[component]pub fn Markdown( content: String, #[props(default)] class: String, #[props(default)] highlight_query: Option<String>,) -> ElementThe 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:
| Context | Component | Class |
|---|---|---|
| Contact notes (read mode) | ContactNoteCard | (default) |
| Call details — transcription | TranscriptionCard | (default) |
| Call details — analysis | AnalysisCard | (default) |
| Contact timeline — call entries | TimelineCallEntry | (default) |
| Contact timeline — note previews | TimelineNoteEntry | prose-sm |
| Messaging — call cards | MessagingCallEntry | prose-sm |
AI-generated interaction notes use structured sections:
## SummaryShort description of the call.
## Key Points- Specific points discussed
## Next Steps- Any follow-up items (omitted if none)API Endpoints
Section titled “API Endpoints”All endpoints require an authenticated session. Only notes with visibility: UserVisible are returned by the API — internal notes are excluded.
| Method | Route | Description |
|---|---|---|
GET | /api/contacts/{contact_id}/notes | List user-visible notes, memory note pinned first, then newest first |
POST | /api/contacts/{contact_id}/notes | Add 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).
Permissions
Section titled “Permissions”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.
UI Components
Section titled “UI Components”ContactNoteCard
Section titled “ContactNoteCard”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)
Search and Filter
Section titled “Search and Filter”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.
Related
Section titled “Related”- Contact Memory — how the system note is generated and used by agents
- Contact — parent module overview