Settings
The settings module provides user account management (email and password changes) and organization-level configuration (telephony, CRM integrations, AI assistant rules, reporting). It uses a multi-level sidebar with URL-based deep linking and permission-gated routing.
Navigation Architecture
Section titled “Navigation Architecture”Settings uses a hierarchical Section → Subsection model defined by two enums in settings_section_type.rs:
| Section | Subsections | Permission |
|---|---|---|
| Account | Credentials, Notifications | Password account / all users |
| Organization | General, AI Context, AI Rules | Settings:Instance:View |
| Team | Members, Roles | Member:Collection:List / Role:Collection:List |
| Channels | Phone, Email, Social | Settings:Instance:View |
| Integrations | Contacts Lookup | Settings:Instance:View |
| Reporting | Daily | Settings:Instance:View |
| Billing | (leaf — no subsections) | Owner only |
Each enum variant provides slug(), label(), description(), and is_accessible(session) methods. Inaccessible sections are hidden from the sidebar.
Deep Linking Routes
Section titled “Deep Linking Routes”Three route variants map to the hierarchy:
#[route("/settings")]SettingsView {},
#[route("/settings/:section")]SettingsSectionView { section: String },
#[route("/settings/:section/:subsection")]SettingsSubsectionView { section: String, subsection: String },All three delegate to SettingsShell, which resolves the URL via resolve_settings_route():
| URL | Resolution |
|---|---|
/settings | Redirects to the first accessible subsection (usually /settings/account/credentials) |
/settings/organization | Redirects to the first accessible subsection of Organization |
/settings/channels/email | Renders the Email page directly |
/settings/billing | Renders Billing as a leaf section (no subsection) |
| Invalid or inaccessible slug | Falls back to the first globally accessible subsection |
Redirects use nav.replace() to avoid polluting browser history. The SettingsResolution enum has four variants: Exact, RedirectToSubsection, RedirectToSection, and NoAccess.
Layout Components
Section titled “Layout Components”SettingsShell (settings_shell_component.rs) is the top-level container:
ViewContainer (max-w-6xl)├─ SettingsMobileNav (md:hidden) — Select dropdown with grouped options└─ Flex (md:flex-row md:gap-8) ├─ SettingsSidebar (md:w-60, md:sticky top-4) — desktop only └─ Main content (flex-1 min-w-0) └─ Page component (dispatched by section/subsection)SettingsSidebar (settings_sidebar_component.rs) renders section headings with subsection links. The active subsection shows a primary-colored left indicator bar and bg-accent/60 background.
On mobile (below md), the sidebar is replaced by a Select dropdown with options grouped by section.
Account Management
Section titled “Account Management”Email Change
Section titled “Email Change”Users change their login email through a password-verified flow.
PUT /api/settings/account/email → Trim and lowercase the new email → Validate format (must contain @ and .) → Verify current password against stored bcrypt hash → Check new email isn't already in use → Update email in email_password_account tableRequest:
pub struct UpdateEmailData { pub new_email: String, pub current_password: String,}Possible responses: Success, IncorrectPassword, InvalidEmail, EmailAlreadyInUse.
Password Change
Section titled “Password Change”Users change their password by providing the current one and a new one (minimum 8 characters).
PUT /api/settings/account/password → Validate new password ≥ 8 characters → Verify current password against stored bcrypt hash → Hash new password with bcrypt (DEFAULT_COST) → Update password_hash in email_password_account tableRequest:
pub struct UpdatePasswordData { pub current_password: String, pub new_password: String,}Possible responses: Success, IncorrectPassword, PasswordTooShort.
Organization Settings
Section titled “Organization Settings”GET /api/settings retrieves and PUT /api/settings updates the organization’s configuration.
pub struct SettingsData { pub name: String, pub twilio_account_sid: Option<String>, pub twilio_auth_token: Option<String>, pub client_lookup_url: Option<String>, pub client_lookup_auth: Option<String>, pub report_daily_enabled: bool, pub report_per_call_enabled: bool, pub report_email_addresses: Vec<String>,}Each subsection maps to a dedicated page component. See the Navigation Architecture section above for the full section → subsection hierarchy and permission requirements.
Page Primitives
Section titled “Page Primitives”Two reusable components provide consistent structure across all settings pages.
SettingsPageHeader
Section titled “SettingsPageHeader”Every settings page starts with a SettingsPageHeader (settings_page_header_component.rs):
SettingsPageHeader { title: SettingsSubsection::OrganizationGeneral.label().to_string(), description: SettingsSubsection::OrganizationGeneral.description().to_string(), actions: rsx! { Button { "New Rule" } }, // optional}Renders a text-2xl title, an optional text-muted-foreground description, and an optional actions slot (top-right).
SettingsSectionCard
Section titled “SettingsSectionCard”Groups related content within a page (settings_section_card_component.rs):
SettingsSectionCard { title: "Identity", description: "Who you are — displayed in your AI's greetings and across the app.", footer: rsx! { SaveButton { status: save_status } }, IdentitySection { form }}Includes an optional header (title + description + action), a body (px-6 py-5), and an optional footer (border-t bg-muted/30) typically used for save buttons.
Avatar
Section titled “Avatar”A new initials badge component (src/ui/avatar_ui.rs) with three sizes (Xs 24px, Sm 32px, Md 36px) and a bg-primary/10 text-primary color scheme. Used in the Team Members list.
run_save Helper
Section titled “run_save Helper”Standardizes save button feedback (src/shared/utils/run_save.rs):
run_save(update_organization_profile_api(form()), save_status, toaster).await;Cycles through Saving → Saved (2 seconds) → Idle. On error, resets to Idle and shows a toast. Auto-detects 403 responses as “Access denied”.
Phone Management (Telephony Tab)
Section titled “Phone Management (Telephony Tab)”Phone numbers are managed within the Telephony tab via the PhoneManagement component — there are no standalone /phones routes. The Telephony tab uses nested sub-tabs: Configuration (Twilio settings) and Phone Numbers (card grid).
PhoneManagement Component
Section titled “PhoneManagement Component”PhoneManagement is a self-contained component at src/mods/settings/components/phone_management_component.rs. It renders:
- Card grid — responsive grid (
grid-cols-1 md:grid-cols-2 lg:grid-cols-3) ofPhoneCardcomponents - Inline detail panel — click a card to expand
PhoneDetailsbelow the grid for viewing/editing agent assignments - Buy flow — a collapsible
BuyPhonesection toggled by the “Buy Number” button
// Permission gatescan_create: PhonePermission::Collection(PhoneCollectionPermission::Create)can_view: PhonePermission::Instance(PhoneInstancePermission::View)can_update: PhonePermission::Instance(PhoneInstancePermission::Update)The component loads phone data, voice agents, and text agents concurrently via use_resource. Errors display through the shared ResourceError component with 403-aware messaging.
CRM Integration Test
Section titled “CRM Integration Test”The Integrations tab includes a live test to verify CRM connectivity before saving.
POST /api/settings/integrations/test-lookup → Send GET request to configured lookup URL with ?phone={number} → Include loquent_x_check header if auth token is configured → Return status code and response bodyThe UI displays a color-coded status badge (green for 2xx, red otherwise) and the raw response body.
Report Email Management
Section titled “Report Email Management”Report email addresses use a HashSet diff approach during updates — the system compares existing addresses against the desired set, deletes removed ones, and inserts new ones within a single database transaction.
Organization Profile
Section titled “Organization Profile”Organizations configure business identity, contact details, operating hours, and AI-specific context. The profile powers AI instruction generation in the agent module.
Get Profile
Section titled “Get Profile”GET /api/settings/organization-profileReturns current profile or defaults if none exists.
Response:
pub struct OrganizationProfileData { // Identity pub description: Option<String>, // "Full-service dental practice" pub legal_name: Option<String>, // "Acme Dental LLC" pub industry: Option<String>, // "Healthcare" pub services: Vec<String>, // ["Cleanings", "Fillings", "Root Canals"] pub founded_year: Option<i16>,
// Contact pub public_phone: Option<String>, pub public_email: Option<String>, pub website_url: Option<String>, pub appointment_url: Option<String>, pub address: Option<String>, pub service_area: Option<String>,
// Languages & Location pub primary_language: String, // ISO 639-1 code: "en", "es", etc. pub secondary_languages: Vec<String>, pub timezone: Option<String>, // IANA timezone: "America/New_York"
// Operating Hours pub hours: Vec<OrganizationHoursData>,
// AI Context pub after_hours_message: Option<String>, // Custom message for closed hours pub voicemail_message: Option<String>, // Voicemail handling instructions pub emergency_contact: Option<String>, pub additional_context: Option<String>, pub compliance_notes: Option<String>,
// Social pub social_links: HashMap<String, String>, // "facebook" → URL}
pub struct OrganizationHoursData { pub day_of_week: i16, // 0=Mon, 1=Tue, ..., 6=Sun pub is_open: bool, pub open_time: Option<String>, // "09:00" pub close_time: Option<String>, // "17:00"}Update Profile
Section titled “Update Profile”PUT /api/settings/organization-profileUpserts the profile and replaces all operating hours in a single transaction.
Processing:
- Existing profile → update all fields +
updated_at - No profile → insert with
created_atandupdated_at - Hours: delete all existing rows, reinsert from payload with
created_atandupdated_atset to the current UTC timestamp - Social links stored as JSON
Organization Context Builder
Section titled “Organization Context Builder”The build_organization_context service formats profile data into markdown for injection into agent system prompts.
Output structure:
## Organization Context
- Description: {description}- Legal Name: {legal_name}- Industry: {industry}- Services: {services}- Founded: {founded_year}
- Phone: {public_phone}- Email: {public_email}- Website: {website_url}- Appointments: {appointment_url}- Address: {address}- Service Area: {service_area}
- Primary Language: {primary_language}- Secondary Languages: {secondary_languages}- Timezone: {timezone}
### Operating Hours- Monday: 09:00 – 17:00- Tuesday: 09:00 – 17:00- Wednesday: Closed...
- After Hours Message: {after_hours_message}- Voicemail Message: {voicemail_message}- Emergency Contact: {emergency_contact}- Additional Context: {additional_context}- Compliance Notes: {compliance_notes}Empty fields are omitted. If no profile exists, returns an empty string.
AI Rules
Section titled “AI Rules”Admins define organization-level rules that shape AI behavior across 5 scoped areas. Each rule targets a specific area and is injected into that area’s prompt as a priority directive.
AI Rule Areas
Section titled “AI Rule Areas”| Area | Enum Value | Where Injected |
|---|---|---|
| Assistant | assistant | Chat assistant system prompt |
| Voice Agent | voice_agent | OpenAI and Gemini realtime agent instructions |
| Task Suggestions | task_suggestions | Text agent reply suggestion context |
| Memory Update | memory_update | Contact memory synthesis prompt |
| Task Creator | task_creator | Task extraction from calls and messages |
Each area uses the same injection pattern — get_active_rules_for_area(org_id, area) fetches active rules, then format_rules_for_prompt() formats them into the prompt section.
Data Model
Section titled “Data Model”Table: organization_ai_rule
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
organization_id | UUID | FK → organization (CASCADE delete) |
title | text | Rule display name |
content | text | Full rule text |
area | text | Target area (default: 'assistant') |
is_active | boolean | Active/inactive toggle (default: true) |
sort_order | integer | Display ordering (default: 0) |
created_at | timestamptz | Creation timestamp |
updated_at | timestamptz | Last update timestamp |
Indexed on (organization_id, area) via idx_organization_ai_rule_org_area.
Request and Response Types
Section titled “Request and Response Types”// Request — used for create and updatepub struct AiRuleData { pub title: String, pub content: String, pub is_active: bool,}
// Responsepub struct AiRule { pub id: String, pub title: String, pub content: String, pub is_active: bool, pub sort_order: i32,}CRUD Endpoints
Section titled “CRUD Endpoints”| Route | Method | Permission | Description |
|---|---|---|---|
/api/settings/ai-rules | GET | Settings:Instance:View | List all rules for the org, ordered by sort_order then created_at |
/api/settings/ai-rules | POST | Settings:Instance:Update | Create a new rule |
/api/settings/ai-rules/{id} | PUT | Settings:Instance:Update | Update a rule (validates org ownership) |
/api/settings/ai-rules/{id} | DELETE | Settings:Instance:Update | Delete a rule (validates org ownership) |
All endpoints validate that the rule belongs to the requesting user’s organization. Update and delete return 404 if the rule doesn’t exist or belongs to a different org.
Prompt Injection
Section titled “Prompt Injection”Each area fetches and formats its rules using a shared service:
let rules = get_active_rules_for_area(org_id, AiRuleArea::Assistant).await?;let section = format_rules_for_prompt(&rules, "ORGANIZATION RULES");This produces a markdown section injected into the prompt:
## ORGANIZATION RULES
The following rules are defined by the organization administrator.You MUST follow these rules in ALL interactions.These rules take precedence over your default behavior.
**RULE: DO NOT CONTACT SIGNED CONTRACTS**When sending a promotional campaign, check the custom field "BB-NT".If it's "Contract Signed", don't send the message.The heading varies by area (e.g., “ORGANIZATION RULES”, “MEMORY UPDATE RULES”). The GET /api/settings/ai-rules endpoint accepts an optional ?area= query parameter to filter rules by area.
UI Components
Section titled “UI Components”The Assistant Rules tab (icon: Sparkles) renders two components:
AiRuleList— card grid with Active/Inactive badges, inline edit, create/delete controls. Shows “No AI rules created yet.” when empty.AiRuleForm— reusable create/edit form with title input, content textarea, and active checkbox. Submit is disabled until both title and content are filled.
Create, edit, and delete buttons are gated behind Settings:Instance:Update. View-only users see the rule list without mutation controls.
API Endpoints
Section titled “API Endpoints”| Route | Method | Description |
|---|---|---|
GET /api/settings/account | GET | Retrieve current user’s email |
PUT /api/settings/account/email | PUT | Change login email |
PUT /api/settings/account/password | PUT | Change password |
GET /api/settings | GET | Retrieve organization settings |
PUT /api/settings | PUT | Update organization settings |
GET /api/settings/organization-profile | GET | Retrieve organization profile |
PUT /api/settings/organization-profile | PUT | Update organization profile |
POST /api/settings/integrations/test-lookup | POST | Test CRM lookup integration |
GET /api/settings/ai-rules | GET | List organization AI assistant rules |
POST /api/settings/ai-rules | POST | Create AI assistant rule |
PUT /api/settings/ai-rules/{id} | PUT | Update AI assistant rule |
DELETE /api/settings/ai-rules/{id} | DELETE | Delete AI assistant rule |
All endpoints require an authenticated session.
Mobile Responsiveness
Section titled “Mobile Responsiveness”The Settings page adapts to screen size:
| Breakpoint | Behavior |
|---|---|
Below md (mobile) | Desktop sidebar hidden, replaced by a grouped Select dropdown for navigation. Forms stack single-column. |
md and above (desktop) | Sticky sidebar with section headings and subsection links. Content area fills remaining width. |
The mobile Select groups subsections under their parent section labels. Leaf sections like Billing appear as standalone items. Selecting an option navigates via the router.
Module Structure
Section titled “Module Structure”src/mods/settings/├── api/│ ├── get_ai_rules_api.rs # List rules│ ├── create_ai_rule_api.rs # Create rule│ ├── update_ai_rule_api.rs # Update rule│ ├── delete_ai_rule_api.rs # Delete rule│ ├── get_organization_profile_api.rs│ ├── update_organization_profile_api.rs│ └── ... # Account, settings, test-lookup├── types/│ ├── settings_section_type.rs # SettingsSection + SettingsSubsection enums│ ├── settings_section_resolver.rs # resolve_settings_route() + SettingsResolution│ ├── ai_rule_data_type.rs # AiRuleData, AiRule│ ├── organization_profile_data_type.rs│ └── ... # Account, settings types├── components/│ ├── settings_shell_component.rs # Shell + mobile nav + route resolution│ ├── settings_sidebar_component.rs # Desktop sidebar navigation│ ├── settings_page_header_component.rs # Standardized page header│ ├── settings_section_card_component.rs # Grouped content card│ ├── pages/ # 13 subsection page components│ │ ├── account_credentials_page.rs│ │ ├── account_notifications_page.rs│ │ ├── organization_general_page.rs│ │ ├── organization_ai_context_page.rs│ │ ├── organization_ai_rules_page.rs│ │ ├── team_members_page.rs│ │ ├── team_roles_page.rs│ │ ├── channels_phone_page.rs│ │ ├── channels_email_page.rs│ │ ├── channels_social_page.rs│ │ ├── integrations_contacts_lookup_page.rs│ │ ├── reporting_daily_page.rs│ │ └── billing_page.rs│ ├── ai_rule_list_component.rs # Rule card grid with CRUD│ ├── ai_rule_form_component.rs # Create/edit form│ ├── organization_general_form_component.rs # Identity, Contact, Languages, Social, Hours│ ├── organization_ai_context_form_component.rs # AI Context, Voicemail, Compliance│ ├── phone_management_component.rs # Phone card grid, detail panel, buy flow│ └── ... # MemberList, EmailList, etc.└── views/ └── settings_view.rs # SettingsView, SettingsSectionView, SettingsSubsectionView