taw-ui
Concept

Domain Surfaces

Canonical UI surfaces for real-world entity types. Provider adapters underneath.

taw-ui started with generic AI tool result components — KpiCard, DataTable, InsightCard. Domain Surfaces extend this into integration-ready surfaces for specific entity categories: issues, events, posts, contacts, and more.

What are Domain Surfaces?

A domain surface is a canonical UI component for a category of real-world entities. Instead of building provider-specific components (GithubIssueCard, LinearIssueCard), we build one canonical surface per entity type and layer provider adapters underneath.

IssueCardGitHub Issues, Linear Issues, Jira Tickets
EventCardGoogle Calendar, Outlook, Cal.com
PostCardX, Instagram, LinkedIn, Threads

Architecture

Each domain surface consists of three layers:

1. Canonical Schema

A Zod schema defining the normalized shape for the entity type. Provider-agnostic, validated at render time. This is the contract.

2. React Component

A polished, motion-native component that renders the canonical data. Provider-aware (shows GitHub/Linear icons) but not provider-dependent.

3. Provider Adapters

Pure transformation functions that map raw provider data into the canonical schema. No auth, no API calls, no side effects. Your app fetches; taw-ui normalizes.

taw-ui vs. Your App

taw-ui is intentionally not an auth library. The boundary is clear:

taw-ui handles
  • Canonical schemas & types
  • React components & rendering
  • Schema validation
  • Provider adapters (pure transforms)
  • Motion & visual polish
  • Loading, error, and streaming states
Your app handles
  • OAuth / authentication
  • Access & refresh tokens
  • API clients & SDKs
  • Data fetching
  • Permissions & scopes
  • Error handling & retries

How It Works

integration flow
// 1. Your app authenticates with the provider
const octokit = new Octokit({ auth: userToken })

// 2. Your app fetches the data
const { data: issue } = await octokit.issues.get({
  owner: "vercel",
  repo: "next.js",
  issue_number: 58234,
})

// 3. Map API response to IssueCard schema
const output = {
  id: issue.number.toString(),
  title: issue.title,
  body: issue.body,
  status: { label: issue.state },
  author: { login: issue.user.login, avatar: issue.user.avatar_url },
  labels: issue.labels.map(l => ({ name: l.name, color: l.color })),
}

// 4. taw-ui renders it
<IssueCard part={{
  toolCallId: "1",
  toolName: "getIssue",
  state: "output-available",
  input: {},
  output,
}} />

Live Examples

IssueCard
vercel/next.jsvercel/next.js#58234Open

When using generateStaticParams with dynamic route segments in the App Router, the build fails with 'Error: Page changed from SSG to SSR'. This happens consistently when the route has nested dynamic segments.

bugapp-router
1y ago
timneutkens
EventCard
MAR10
Work

Q1 Planning — Product & Engineering Sync

Today · 6:00 PM 7:30 PM(1h 30m)

Quarterly planning session to align on product roadmap priorities, engineering capacity, and key deliverables for Q1. Please review the pre-read doc before joining.

Conference Room 4B
SSarah Chen(organizer)
James WilsonPriya PatelAlex KimMaria Garcia
PostCard

Design Philosophy

Category first, provider second. We start with a canonical IssueCard, not a GithubIssueCard. The schema defines what an issue looks like across all providers. Adapters handle the differences.

Pure transformations. Provider adapters are pure functions — inline data mappings with no network calls, no auth, no side effects. They take raw provider data in and return canonical data out.

Graceful degradation. Every field beyond id, provider, title, and status is optional. The component renders beautifully with minimal data and gets richer as more fields are provided.

No SDK lock-in. Adapter input types are loose interfaces, not imports from @octokit/types or @linear/sdk. Any object with the right shape works.

Available Surfaces