Locara

11 — Components (@locara/components)

The UI primitive layer. Designed for two audiences:

  1. Human developers who want to ship a polished app without designing every UX detail.
  2. AI coding assistants who can read, modify, and extend components in the user’s repo.

Distribution model: shadcn-style copy-in source

Locara components are not installed as an npm package. They’re copied into the user’s repo as source code, via the CLI:

locara add chat
# → copies src/components/ui/chat.tsx into your project

locara add transcript-stream doc-dropzone
# → copies multiple at once

locara add --all
# → copies all curated components

The user owns the source. They edit it freely. Updates are pulled by re-running locara add, with optional diff-and-merge.

This approach (pioneered by shadcn/ui) has three benefits for Locara specifically:

  1. AI-friendly. LLMs read the actual component code in the repo, modify it, write back. No black-box library to fight. See v0 + community design systems.
  2. Customization-first. Want square corners on the chat bubble? Edit the file. No theme override systems, no className wrestling.
  3. Reviewable. Each app’s component code is in scope for security review. The reviewer sees exactly what’s running.

Tradeoff: maintenance shifts to the user. Bug fixes upstream require re-add + manual merge. Acceptable cost for the benefits above.

Curated components (v1)

These ship in the official Locara registry on day 1:

Chat / conversation

  • <Chat> — Conversation-style UI with streaming, message history, role indicators.
  • <ChatInput> — Auto-resize textarea with submit + tool buttons.
  • <MessageBubble> — Single message with markdown rendering, code blocks.
  • <ToolInvocation> — Inline display of tool calls during a chat.

Document / file handling

  • <DocDropzone> — Drag-drop zone for files, with type filtering, multi-file support.
  • <FilePicker> — Powerbox-mediated file picker wrapper.
  • <DocPreview> — PDF / image / text preview pane.

Transcription / audio

  • <AudioInput> — Mic input control with level visualization.
  • <TranscriptStream> — Live transcription display with segment highlighting.
  • <AudioPlayer> — Standard playback control.

Search / data

  • <SearchBar> — Hybrid search input (FTS + vector).
  • <ResultsList> — Result list with relevance scoring, snippets.
  • <DataGrid> — Sortable, filterable table over sqlite query results.

Layout / shell

  • <AppShell> — Top-level layout (sidebar, header, content).
  • <Sidebar> — Standard sidebar nav.
  • <CommandPalette> — ⌘K-style command palette.
  • <EmptyState> — Standard empty / loading / error states.

Settings / model management

  • <ModelPicker> — Compact dropdown (native select) for in-panel use.
  • <ModelSelector> — Cursor-style rich picker with per-row download flow, capability badges, RAM-based disabling.
  • <ModelDownloader> — Standalone model-fetch surface with hash, size, progress, throughput, ETA.
  • <CapabilityIndicator> — Show this app’s capability profile (for transparency).

AI affordances (new in v0.0.3)

  • <ChatComposer> — Composite: PromptBox + ModelSelector + ModeSwitcher + AttachmentChip rail + SlashMenu trigger + voice button.
  • <ListeningSurface> — Composite: VoiceOrb / WaveformBars + StatusPill + live partial transcript + Stop/Pause.
  • <ListeningPill> — Granola-style floating recording indicator. Floats over any container; draggable.
  • <VoiceOrb> — Pulsating gradient orb. State machine (idle/listening/thinking/speaking/error), amplitude-reactive.
  • <WaveformBars> — Animated audio equaliser bars. Live (amplitude-driven) or static (decorative).
  • <CitationChip> + <SourceCard> — Perplexity-style inline citation pill + companion source card.
  • <ToolCallCard> — Cursor-style collapsible tool invocation with status + Approve/Reject for capability-gated tools.
  • <Reasoning> — Collapsible chain-of-thought block. Streams “Thinking…” while in progress; settles into “Thought for Ns.”
  • <ThinkingIndicator> — Pre-token cue. Three variants: shimmer (Anthropic), dots (iMessage), pulse (minimalist).
  • <StreamingText> — Inline wrapper with a trailing blinking caret while streaming.
  • <ContextMeter> — Visualises context-window usage as a bar or pill, with warn/danger thresholds.
  • <ModeSwitcher> — Cursor Ask/Edit/Agent pill tabs (or arbitrary modes).
  • <AttachmentChip> — File/image/audio pill rail above the composer.
  • <StatusPill> — Linear-style colored-dot + label. Tones + optional pulse for live states.
  • <EmptyStateCards> — Starter-prompt grid for fresh canvas.
  • <FollowupRow> — Perplexity/Claude follow-up suggestion chips.
  • <AcceptReject> — Notion-AI accept-edit-discard ribbon.
  • <SlashMenu> — Caret-anchored command list with fuzzy filtering.
  • <CommandPalette> — Linear-grade ⌘K with sticky group headers + kbd hints.
  • <HoverActionRow> — Clean row with trailing actions revealed on hover.
  • <AIText> — Granola’s gray-AI / strong-user color treatment; whole-block or mixed-pieces.

Misc

  • <MarkdownView> — Streaming markdown.
  • <CodeBlock> — Code with copy button, language tag.
  • <Spinner>, <Skeleton>, <Tooltip>, <Kbd> — Standard utilities.

The full catalog with live previews lives at /components. Community registries can extend.

Component schema

Each component is published with metadata at registry/components/<name>.json:

{
  "id": "chat",
  "version": "0.1.0",
  "displayName": "Chat",
  "description": "Conversation-style UI with streaming + tool calls.",
  "license": "MIT",
  
  "files": [
    { "path": "src/components/ui/chat.tsx", "url": "..." },
    { "path": "src/components/ui/message-bubble.tsx", "url": "..." }
  ],
  
  "dependencies": {
    "react": "^18.0.0",
    "@locara/sdk": "^0.1.0"
  },
  
  "tailwind_config_additions": { ... },
  "uses_components": ["message-bubble", "markdown-renderer"]
}

The CLI’s locara add chat reads the schema, downloads the source files into the project, adds Tailwind config additions, and pulls in transitively-required components.

Stack assumptions

Components are built with:

  • React 18+ as the rendering primitive (open: support other frameworks?).
  • TypeScript with strong types.
  • Tailwind CSS for styling.
  • Radix UI primitives for accessibility / behavior layers (modal, popover, dropdown, etc.).
  • @locara/sdk for any data access (no direct fetch calls inside components).

These are committed for v1. The shadcn-flavored ecosystem is large enough to share patterns; reinventing in another framework adds friction.

(open) Vue / Svelte support — likely v2+. Same component schema, alternative implementations published in parallel registries.

Community / third-party registries

Anyone can publish a component registry. The CLI supports adding registries:

locara registry add design-system https://my-org.com/locara-components.json
# Then: locara add --from=design-system button

Registries are JSON manifests served from any HTTPS endpoint. The CLI fetches, verifies, copies. This is the same model as shadcn — encourages a thriving third-party ecosystem.

Locara’s official registry is one option among many; design choices avoid lock-in.

For trust:

  • Locara-official registry components have a Locara signature; CLI verifies.
  • Third-party registries are unsigned by default; CLI shows a warning (“unverified registry — review the source you’re about to copy”).
  • A registry can opt into Locara verification (PR to register the registry’s domain + signing key); verified registries don’t trigger the warning.

Component update workflow

When new component versions are published:

locara add chat
# CLI detects existing chat.tsx, shows diff vs new version, asks to merge
# Options: overwrite / skip / open in editor for manual merge

Components track their origin:

// src/components/ui/chat.tsx
// locara: chat@0.1.0 (locara-official)
// To update: locara add chat

The header comment lets the CLI detect outdated copies and offer updates.

AI authoring affordances

Components are designed to be modified by LLMs in the user’s editor:

  1. Verbose prop docs. Every component has JSDoc on its props. Cursor / Claude tab-completion shows them.
  2. Single-purpose components. Each component does one thing. Easier for LLMs to reason about than mega-components.
  3. Declarative children. Composition via children, not config props. LLMs handle JSX naturally.
  4. No magic. No forwardRef gymnastics, no opaque hooks, no global state. Plain React + Tailwind.

For example, when a user asks Cursor “add a voice-input button to the chat,” the LLM:

  1. Reads src/components/ui/chat.tsx.
  2. Sees the existing <ChatInput> and where it renders.
  3. Imports <AudioInput> from src/components/ui/audio-input.tsx.
  4. Wires the button into the chat input toolbar.
  5. Saves both files.

This works because the source is readable and the components are small.

Theming

Components use Tailwind utility classes. Apps can:

  1. Edit components directly. Want different colors? Edit the file.
  2. Override Tailwind config. Standard Tailwind theming via tailwind.config.js.
  3. Use CSS variables for runtime theming. Locara components reference CSS variables (--locara-primary, etc.) so users can switch dark/light/custom at runtime.

There’s no separate “theme” abstraction. Tailwind + CSS variables is the theme system.

What’s NOT in components

  • No data fetching components. Components don’t make API calls. The app does, components render data.
  • No state management library. No global stores, no Redux. Apps pick their own state library; components accept props and callbacks.
  • No routing. Apps pick their own routing.
  • No form library. Form composition is up to the app.
  • No i18n. v1 is English-only. Future versions add translation hooks.

This keeps the components small, focused, and easy to compose.

Cross-references