npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@swarmify/harness-ui

v0.1.0

Published

Universal React generative UI system for AI agents. Schema-validated components that work in CSR, SSR, RSC, Electron, and React Native.

Readme

harness-ui

Universal React generative UI system for AI agents.

Installation

bun add harness-ui

Peer dependencies: react >= 18.0.0

Tailwind CSS (required)

Components use Tailwind CSS classes. Add harness-ui to your content paths:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,ts,jsx,tsx}',
    './node_modules/harness-ui/src/**/*.{js,ts,jsx,tsx}',
  ],
}

What makes harness-ui different

  • Universal React: Works in CSR, SSR, RSC, Electron, React Native
  • Schema-first: Zod schemas validate LLM output before render
  • Registry pattern: LLM outputs component names, client renders from registry
  • MCP discovery: Components auto-exposed to LLM as tools
  • Bandwidth efficient: JSON payloads, not streamed components

Quick comparison

| Feature | harness-ui | Vercel streamUI | AI Elements | |---------|-----------|-----------------|-------------| | Generative UI | ✓ Built-in | ✓ Experimental | ✗ Manual | | CSR support | ✓ | ✗ (RSC only) | ✓ | | SSR support | ✓ | ✓ | ✓ | | Cross-platform | ✓ | ✗ (Next.js only) | ✗ (Next.js) | | Schema validation | ✓ Zod | ✗ | ✗ | | MCP discovery | ✓ | ✗ | ✗ | | Production ready | ✓ | ✗ (experimental) | ✓ |

Architecture

harness-ui uses client-side rendering with a registry pattern:

┌─────────────┐
│     LLM     │  Outputs: { component: "email_card", props: {...} }
└──────┬──────┘
       │ JSON (~50 bytes)
       ▼
┌─────────────┐
│   Client    │  1. Registry lookup
│  (Browser/  │  2. Zod validation
│  Electron)  │  3. Component render
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ <EmailCard  │  Rendered component
│   {...} />  │
└─────────────┘

vs Vercel streamUI (server-side):

┌─────────────┐
│     LLM     │  Tool call: get_weather
└──────┬──────┘
       │
       ▼
┌─────────────┐
│   Server    │  render: function*() { yield <Card/> }
│  (Next.js)  │  Executes during tool call
└──────┬──────┘
       │ Streamed RSC (~500+ bytes)
       ▼
┌─────────────┐
│   Client    │  Receives serialized component
└─────────────┘

Why client-side?

  • Works anywhere React works (not locked to Next.js RSC)
  • Works offline (components are local code)
  • Debuggable (JSON is inspectable)
  • Bandwidth efficient (especially for data-heavy UIs like spreadsheets)

Registry Pattern

Components are registered client-side:

import { componentRegistry } from 'harness-ui';

const Component = componentRegistry['email_card'];
// Returns: EmailCard component

The LLM outputs component names as strings:

{
  "component": "email_card",
  "props": {
    "messageId": "msg_123",
    "from": { "name": "Alice", "email": "[email protected]" },
    "subject": "Project update",
    "snippet": "Latest progress report..."
  }
}

Your client validates and renders:

import { componentRegistry, emailCardSchema } from 'harness-ui';

const result = emailCardSchema.safeParse(llmOutput.props);
if (result.success) {
  const Component = componentRegistry[llmOutput.component];
  return <Component {...result.data} onAction={handleAction} />;
}

This works in CSR, SSR, and RSC - the registry is just a static import.

Schema System

Each component exports two schemas:

Props Schema

What the LLM sends to render the component:

// email-card.tsx
export const emailCardSchema = z.object({
  messageId: z.string().describe('Unique message identifier'),
  from: z.object({
    name: z.string(),
    email: z.string(),
  }).describe('Sender information'),
  subject: z.string().describe('Email subject line'),
  snippet: z.string().optional().describe('Preview text'),
});

Actions Schema

What the component can emit when the user interacts:

// email-card.tsx
export const emailCardActions = {
  reply: {
    description: 'User wants to reply to this email',
    params: z.object({
      body: z.string().describe('Reply content'),
    }),
  },
  archive: {
    description: 'User archived the email',
    params: z.object({}),
  },
  delete: {
    description: 'User deleted the email',
    params: z.object({}),
  },
};

Components call onAgentAction(actionName, params) when users interact:

<EmailCard
  {...props}
  onAgentAction={(action, params) => {
    // action = 'reply' | 'archive' | 'delete'
    // params = { body: '...' } for reply, {} for others
    sendToAgent({ action, params });
  }}
/>

MCP Server

harness-ui includes an MCP server that exposes components as tools. LLMs can discover available components and their schemas automatically.

import { createHarnessUIServer } from 'harness-ui/mcp';

const server = createHarnessUIServer();
// Exposes: render_email_card, render_ask_permission, etc.

The server auto-generates tool definitions from component schemas:

{
  "tools": [
    {
      "name": "render_email_card",
      "description": "Display an email with sender, subject, and actions. Actions: reply, archive, delete",
      "inputSchema": {
        "type": "object",
        "properties": {
          "messageId": { "type": "string", "description": "Unique message identifier" },
          "from": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" } } },
          "subject": { "type": "string", "description": "Email subject line" }
        },
        "required": ["messageId", "from", "subject"]
      }
    }
  ]
}

When validation fails, the server returns actionable errors:

{
  "error": "validation_failed",
  "issues": [
    { "path": ["from", "email"], "message": "Required" }
  ],
  "hint": "The 'from' field requires both 'name' and 'email' properties"
}

Design Language

harness-ui components follow a premium, macOS-native aesthetic. Every component should feel like it belongs in a high-end desktop application, not a web dashboard.

Display Modes

  • All generative components accept an optional mode prop ('default' | 'compact') via GenerativeComponentProps.
  • default (omit or set): spacious layout with full details.
  • compact: tighter padding/typography; secondary details may be hidden (e.g., descriptions, attendee lists, extra buttons) while core info stays visible.
  • Use compact when space is constrained: week-row calendars, sidebars, narrow columns, multi-card grids.
  • Containers can propagate mode (e.g., event_list sets mode="compact" for all items) but child items can override.
  • If mode is absent or unknown, components fall back to default for backward compatibility.

Philosophy: Craft Over Features

Functionality doesn't create awe - craft does.

The Mac feeling comes from animations that feel alive, typography that breathes, moments of unexpected delight, and everything feeling intentional.

| Moment | Windows Feel | Mac Feel | |--------|--------------|----------| | Agent starts | "Processing..." spinner | Subtle pulse, smooth fade-in | | Email list | Plain text dump | Rich cards with avatars | | Empty state | "No results" | Illustration + "You're all caught up" | | Error | Red text, stack trace | Friendly message + retry | | Completing task | Nothing | Subtle celebration |

Every UI decision should ask: "Does this feel like Mac or Windows?"

Core Principles

| Principle | Description | |-----------|-------------| | Monochrome palette | Use zinc grays and white-alpha overlays. Avoid saturated colors. | | Typography-driven state | Indicate state through font weight and opacity, not colored indicators. | | Subtle interactions | Hover states should be gentle, not dramatic. | | Generous spacing | Components should breathe. Never feel cramped. | | No visual noise | Every element must earn its place. Remove decorative elements. | | No toasts or colored indicators | No green checkmarks, no red crosses. Use text changes, subtle animations. | | Hide the plumbing | Technical IDs (tool_call_id) never shown to users. | | Icons must be semantic | Gmail logo for email, not generic Mail icon. Skip decorative icons. |

Color Palette

Backgrounds (use white-alpha for consistency across themes):

Card default:     bg-white/[0.03]
Card elevated:    bg-white/[0.05]
Card hover:       bg-white/[0.08]
Button hover:     bg-white/[0.05]
Button active:    bg-white/[0.10]

Borders:

Default:          border-white/[0.08]
Hover:            border-white/[0.12]
Dividers:         border-white/[0.08]

Text (zinc scale for predictable contrast):

Primary:          text-zinc-100    (headings, important)
Secondary:        text-zinc-300    (body text)
Tertiary:         text-zinc-400    (descriptions, read state)
Muted:            text-zinc-500    (timestamps, metadata)
Disabled:         text-zinc-600    (inactive elements)

State Indication

Do NOT use:

  • Colored dots (blue, green, red)
  • Colored left/top borders as accents
  • Saturated background colors for categories
  • Colored badges for priority/status

Instead use:

  • Font weight: font-semibold for unread/active, normal for read/inactive
  • Text opacity: text-zinc-100 for active, text-zinc-400 for inactive
  • Background opacity: bg-white/[0.05] for selected, bg-white/[0.03] for normal

Card Styling

IMPORTANT: Components must NOT include borders, backgrounds, or rounded corners.

harness-ui components are rendered inside containers that control visual styling. Different hosts (desktop apps, floating windows, embedded views) apply their own border/background/rounding preferences. If components include their own card styling, it creates a double-border effect.

Wrong - component has its own card styling:

// DON'T DO THIS
<div className="rounded-xl border border-white/[0.08] bg-white/[0.03] p-4">
  <h3>{title}</h3>
  <p>{content}</p>
</div>

Correct - component only has internal padding:

// DO THIS
<div className="p-4">
  <h3>{title}</h3>
  <p>{content}</p>
</div>

The container/wrapper (controlled by the host app) applies the visual styling:

// Host app wraps component with desired styling
<div className="rounded-xl border border-white/[0.08] bg-white/[0.03]">
  <MyComponent {...props} />
</div>

Why this matters:

  • Not every consumer wants rounded borders (some prefer sharp/square UI)
  • Different contexts need different rounding (cards vs floating windows vs inline)
  • Prevents double-border visual artifacts
  • Gives host apps full control over visual consistency

What components CAN include:

  • Internal padding (p-4, px-3 py-2, etc.)
  • Internal spacing between elements (space-y-3, gap-2)
  • Text colors and typography
  • Internal dividers (border-t border-white/[0.08] between sections)

What components must NOT include:

  • Outer border (border, border-white/[0.08])
  • Outer background (bg-white/[0.03], bg-neutral-900)
  • Outer rounding (rounded-xl, rounded-lg)
  • Box shadows (shadow-xl)

Buttons

Primary (rare, for main actions):

className="rounded px-3 py-1.5 text-sm text-zinc-100
           bg-white/[0.10] hover:bg-white/[0.15]"

Secondary (most common):

className="rounded px-3 py-1.5 text-sm text-zinc-400
           hover:bg-white/[0.05] hover:text-zinc-300"

Icon buttons:

className="rounded p-1.5 text-zinc-500
           hover:bg-white/[0.05] hover:text-zinc-300"

Avatars

When brand icons unavailable, use monochrome initials:

className="flex h-10 w-10 items-center justify-center rounded-full
           bg-zinc-700/50 text-sm font-medium text-zinc-300"

Never use colored backgrounds for avatars based on category/type.

Typography Hierarchy

Title:            text-sm font-semibold text-zinc-100
Subtitle:         text-sm text-zinc-400
Body:             text-sm text-zinc-300
Caption:          text-xs text-zinc-500

Badges

Muted, not attention-grabbing:

className="rounded bg-zinc-700/50 px-1.5 py-0.5 text-xs text-zinc-400"

Expanded Content

When cards expand to show more content, use a subtle divider:

className="mt-4 pt-4 border-t border-white/[0.08]"

Text Utilities

Always decode HTML entities before display:

import { decodeHtmlEntities } from '../lib/utils';
// Use on any user-generated or API-sourced text
{decodeHtmlEntities(snippet)}

Animations

Animations should be subtle and purposeful:

  • Fade-ins: Use for appearing elements, 150-200ms duration
  • Staggered cascade: When showing multiple cards, stagger by 50ms each
  • Hover transitions: Border/background changes only, 150ms
  • No springs or bounces: No shaky, playful effects
  • Shimmer: Use on interactive text to signal clickability without underlines
// Fade-in on mount
className="animate-in fade-in duration-200"

// Staggered list items
style={{ animationDelay: `${index * 50}ms` }}

Copy & Text

Tense reflects state:

  • In progress: "Reading emails..." / "Analyzing data..."
  • Completed: "Read 5 emails" / "Analyzed 3 reports"

Empty states: Never just "No results". Provide context:

  • "No emails yet" with a subtle illustration
  • "You're all caught up" for cleared inbox
  • "Start a conversation" for new sessions

Error messages: Friendly, not technical:

  • Bad: "Error: ECONNREFUSED 127.0.0.1:3000"
  • Good: "Couldn't connect. Check your internet and try again."

Anti-Patterns

| Avoid | Why | Use Instead | |-------|-----|-------------| | bg-blue-500/20 | Saturated, Gmail-like | bg-zinc-700/50 | | border-l-2 border-l-purple-500 | Slack/Discord pattern | Uniform borders | | bg-[rgb(12,12,16)] | Hardcoded, doesn't adapt | bg-white/[0.03] | | Blue dot for unread | Visual noise | Bold text | | text-yellow-400 for starred | Too prominent | text-zinc-300 | | p-3 on cards | Too cramped | p-4 | | rounded-lg | Not premium enough | rounded-xl | | "No results" | Feels broken | Contextual empty state | | "Error: CODE" | Technical, scary | Friendly explanation | | Spring/bounce animations | Playful, not premium | Subtle fades |

Services Configuration

Components that require external services (transcription, icons) must be configured via HostProvider:

import { HostProvider, type HostAPI } from 'harness-ui';

const hostAPI: HostAPI = {
  services: {
    transcription: { url: 'wss://your-api.com/api/v1/transcribe' },
    icons: { url: 'https://your-api.com/api/v1/icons/resolve' },
  },
  auth: {
    getToken: async () => ({ ok: true, token: 'your-token' }),
  },
};

<HostProvider api={hostAPI}>
  <App />
</HostProvider>

If services are not configured, components will show appropriate error states rather than silently failing.

Icon System

Components that display external brands (email senders, integrations) can resolve icons via a centralized service. Configure the URL via HostProvider.api.services.icons.url.

API Contract

Your icon service should accept POST requests:

POST {services.icons.url}
{
  "queries": [
    { "type": "email", "value": "Google Calendar <[email protected]>" },
    { "type": "domain", "value": "github.com" },
    { "type": "brand", "value": "Notion" }
  ]
}

Response:
{
  "icons": {
    "email:Google Calendar <[email protected]>": "https://cdn.example.com/google-calendar.png",
    "domain:github.com": "https://cdn.example.com/github.png",
    "brand:Notion": "https://cdn.example.com/notion.png"
  }
}

Query Types

| Type | Resolution Logic | Example | |------|------------------|---------| | email | Extract brand from sender name/domain | "Google Calendar <[email protected]>" | | domain | Map domain to brand icon | "github.com" | | brand | Direct brand name lookup | "Notion" |

If no icon service is configured, components fall back to showing initials.

Components

Interactive Components

| Component | Purpose | |-----------|---------| | AskPermission | iOS-style permission request (allow once/session/always, deny) | | AskUserQuestion | Collect user input with text fields, selects, checkboxes | | EditablePreview | Editable content with approve/reject actions | | InputForm | Multi-field form with validation | | FileInput | File upload with drag-and-drop | | PhotoSelector | Image selection grid | | PromptCard | Multi-step prompts with inputs | | RichTextEditor | Tiptap-based rich text editing | | ReviewArtifacts | Review and approve/reject generated content |

Data Display

| Component | Purpose | |-----------|---------| | EmailCard | Email with sender avatar, subject, snippet, actions | | EmailList | Scrollable list of emails | | EventCard | Calendar event with time, location, attendees | | EventList | List of calendar events | | ContactCard | Contact info with avatar, phone, email | | TransactionCard | Financial transaction with amount, status | | MetricCard | KPI with value, trend indicator, sparkline | | TableCard | Data table with sortable columns | | ChartCard | Charts (bar, line, pie) via lightweight renderer | | LinkCard | URL preview with favicon, title, description |

Media

| Component | Purpose | |-----------|---------| | MediaGallery | Image/video grid with lightbox | | VideoPlayer | Video with playback controls | | HeadshotGallery | Portrait photo grid for selection | | PlacesMap | Leaflet map with location markers |

Progress and Status

| Component | Purpose | |-----------|---------| | ProgressCard | Progress bar with percentage | | TaskProgress | Multi-step task with current step indicator | | GenerationProgress | AI generation status with stages | | AgentStatus | Agent execution state display |

Content

| Component | Purpose | |-----------|---------| | Brief | Summary card with key points | | LearningCard | Lesson/exercise with progress | | MealCard | Meal logging with nutrition info | | MeetingSummary | Meeting notes with action items | | MeetingNotepad | Live transcription notepad | | TwitterThread | Twitter thread preview | | PrioritizedTodoList | Todo list with priority levels |

Layout and Animation

| Component | Purpose | |-----------|---------| | FlexStack | Flexible layout container | | StreamingText | Typewriter text animation | | StreamingContainer | Container for streaming content | | FadeIn, Stagger, Pulse, Shimmer | Animation primitives |

Streaming Infrastructure

Framework-agnostic streaming text support. Works with any data source (SSE, fetch streams, WebSocket, IPC).

Architecture

| Export | Purpose | |--------|---------| | StreamingProvider | React context provider - wrap your app | | useStreamingController() | Push chunks from data sources | | useStreamingText(streamId) | Subscribe to stream updates | | ConnectedStreamingText | Auto-subscribing StreamingText component |

Usage

// 1. Wrap app with provider
import { StreamingProvider } from 'harness-ui';

<StreamingProvider>
  <App />
</StreamingProvider>

// 2. Push chunks from any source
import { useStreamingController } from 'harness-ui';

const { start, pushChunk, complete } = useStreamingController();

// SSE example (standard for LLM APIs)
const eventSource = new EventSource('/api/chat');
start('response-1');
eventSource.onmessage = (e) => pushChunk('response-1', e.data);
eventSource.onerror = () => complete('response-1');

// 3. Render streaming content
import { ConnectedStreamingText } from 'harness-ui';

<ConnectedStreamingText streamId="response-1" />

Controller Methods

| Method | Purpose | |--------|---------| | start(streamId, initialText?) | Start new stream, clears existing | | pushChunk(streamId, chunk) | Append text chunk | | complete(streamId) | Mark stream as complete | | reset(streamId) | Clear stream entirely |

Key Files

| File | Purpose | |------|---------| | src/lib/streaming.tsx | Provider, hooks, store | | src/components/connected-streaming-text.tsx | Auto-subscribing component | | src/components/streaming-text.tsx | Presentational component |

Usage in Agents

Agents declare ui_components in YAML to get render_* tools:

ui_components:
  - email_card
  - metric_card

The agent can then call render_email_card({ ... }) to display rich UI.