Skip to content

Entity UI Primitives

Loquent uses a set of shared UI primitives to standardize how entity modules (agents, analyzers, text agents, knowledge bases, plan templates, tasks) render list and detail views. These components enforce consistent layout, responsive behavior, and interaction patterns across every entity type.

Responsive grid with a built-in empty state. Renders 1 column on mobile, 2 on tablet, 3 on desktop.

File: src/shared/components/entity_grid_component.rs

#[component]
pub fn EntityGrid(
empty_message: String, // text shown when is_empty is true
is_empty: bool, // whether the collection has zero items
children: Element, // grid items (cards wrapped in Links)
) -> Element

Usage:

EntityGrid {
empty_message: "No agents yet. Create one to get started.",
is_empty: agents.is_empty(),
for agent in agents.iter() {
Link { to: Route::AgentDetails { id: agent.id },
AgentCard { agent: agent.clone() }
}
}
}

The grid applies grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3. When empty, it renders a dashed border placeholder with the message centered.

Unified card shell for entity listings. Provides consistent structure, hover lift effect, and metadata layout.

File: src/shared/components/entity_card_component.rs

#[component]
pub fn EntityCard(
name: String, // primary label
#[props(default = "Untitled")]
name_fallback: String, // shown when name is empty
status_badge: Option<Element>, // badge next to the name
description: Option<String>, // 1-2 line preview (pre-stripped)
metadata: Option<Element>, // row of Badge components
children: Element, // additional custom content
) -> Element

Usage:

EntityCard {
name: agent.name.clone(),
description: Some(strip_markdown(&agent.prompt)),
metadata: rsx! {
Badge { variant: BadgeVariant::Secondary, "{agent.provider_name()}" }
Badge { variant: BadgeVariant::Outline, "{agent.model_label()}" }
},
}

Cards display a hover lift (hover:shadow-md hover:-translate-y-0.5). Empty names render as Untitled in muted italic. Descriptions are clamped to 2 lines via line-clamp-2.

Fixed bottom bar with blur backdrop for save/delete buttons on detail and create views.

File: src/shared/components/sticky_action_bar_component.rs

#[component]
pub fn StickyActionBar(children: Element) -> Element

Usage:

StickyActionBar {
Button { variant: ButtonVariant::Destructive, onclick: on_delete, "Delete" }
SaveStatusButton { status: save_status, disabled: !is_dirty, onclick: on_save }
}

The bar sits at z-10 with bg-background/80 backdrop-blur-sm. It uses max-w-4xl centered layout with compact py-2 padding at all breakpoints.

Modal dialog for confirming destructive or significant actions. Supports three variants, optional text confirmation, keyboard dismiss, and click-away close.

File: src/ui/confirm_dialog_ui.rs

#[component]
pub fn ConfirmDialog(
#[props(default)]
variant: ConfirmDialogVariant, // Destructive | Warning | Info
title: String, // e.g. "Delete Agent"
description: String, // explanatory text
#[props(default = "Confirm")]
confirm_label: String,
#[props(default = "Cancel")]
cancel_label: String,
#[props(default)]
loading: bool, // shows spinner, disables buttons
confirm_text: Option<String>, // require typing exact text to confirm
#[props(default)]
class: String, // extra Tailwind classes
on_confirm: EventHandler<()>,
on_cancel: EventHandler<()>,
) -> Element

Standard delete confirmation:

if show_delete_dialog() {
ConfirmDialog {
variant: ConfirmDialogVariant::Destructive,
title: "Delete Agent",
description: "This action cannot be undone. The agent and its configuration will be permanently removed.",
confirm_label: "Delete",
loading: is_deleting(),
on_confirm: move |_| delete_agent.send(agent_id),
on_cancel: move |_| show_delete_dialog.set(false),
}
}

With text confirmation (contact delete):

ConfirmDialog {
variant: ConfirmDialogVariant::Destructive,
title: "Delete Contact",
description: "This will permanently delete the contact and all associated data.",
confirm_label: "Delete",
confirm_text: Some("DELETE".to_string()),
on_confirm: move |_| delete_contact.send(contact_id),
on_cancel: move |_| show_dialog.set(false),
}

When confirm_text is set, an input field appears and the confirm button stays disabled until the user types the exact string. The dialog dismisses on Escape or backdrop click (unless loading is true).

Converts markdown text to plain text for card previews. Uses pulldown_cmark to walk the AST and extract visible text content.

File: src/shared/utils/strip_markdown.rs

pub fn strip_markdown(md: &str) -> String
strip_markdown("## Role & Objective") // → "Role & Objective"
strip_markdown("You are **Coco**, the *friendly* assistant")
// → "You are Coco, the friendly assistant"
strip_markdown("- First\n- Second\n- Third") // → "First Second Third"

Strips headings, bold, italic, lists, and image alt text. Collapses whitespace. Returns a trimmed plain string.

All entity views follow the same responsive pattern:

BreakpointGrid columnsForm layoutAction bar
<768px (mobile)1 columnSingle column, stacked fieldsCompact py-2
768-1023px (tablet)2 columnsSame as mobileSame
≥1024px (desktop)3 columnsMulti-column gridsSame

Voice and tool configuration forms within agent details stack their 2-column grids to single column below the md breakpoint. SelectTrigger components use w-full class merging to fill their container at all widths.