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.
Components
Section titled “Components”EntityGrid
Section titled “EntityGrid”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)) -> ElementUsage:
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.
EntityCard
Section titled “EntityCard”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) -> ElementUsage:
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.
StickyActionBar
Section titled “StickyActionBar”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) -> ElementUsage:
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.
ConfirmDialog
Section titled “ConfirmDialog”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<()>,) -> ElementStandard 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).
Utilities
Section titled “Utilities”strip_markdown
Section titled “strip_markdown”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) -> Stringstrip_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.
Responsive Breakpoints
Section titled “Responsive Breakpoints”All entity views follow the same responsive pattern:
| Breakpoint | Grid columns | Form layout | Action bar |
|---|---|---|---|
<768px (mobile) | 1 column | Single column, stacked fields | Compact py-2 |
768-1023px (tablet) | 2 columns | Same as mobile | Same |
≥1024px (desktop) | 3 columns | Multi-column grids | Same |
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.