Install
$ agentstack add skill-leo-atienza-atlas-claude-ai-native-ui ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
AI-Native UI
When to Use This Skill
Load when building any UI with LLM integration: chat interfaces, AI-generated content, smart search, content generation tools, or any feature that streams AI responses to users.
This is Tier 4 (Generative) in the Vanguard architecture. Load web-l100 (SK-083) for the full tier system.
The Generative Tier
AI streams are a fundamentally different rendering model. Unlike T0-T3 where the final content is known at render time, T4 content builds up token-by-token with structural uncertainty. The UI must:
- Show useful content immediately (PPR shell + cached content)
- Progressively reveal AI-generated content as it streams
- Handle tool invocations mid-stream (AI calls tools → UI renders components)
- Gracefully handle interruption (user cancels, network drops)
Core Pattern: streamText + Route Handler
Server: Route Handler
// app/api/chat/route.ts
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
system: 'You are a helpful assistant.',
messages,
tools: {
getWeather: {
description: 'Get weather for a location',
parameters: z.object({
location: z.string().describe('City name'),
}),
execute: async ({ location }) => {
const weather = await fetchWeather(location);
return weather;
},
},
},
});
return result.toDataStreamResponse();
}
Client: useChat Hook
// components/chat.tsx
'use client';
import { useChat } from '@ai-sdk/react';
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat({
api: '/api/chat',
});
return (
{messages.map(message => (
{message.content}
{/* Render tool invocations as components */}
{message.toolInvocations?.map(invocation => (
))}
))}
{isLoading ? (
Stop
) : (
Send
)}
);
}
Tool Invocations → React Components
The killer pattern: AI calls tools, each tool invocation maps to a React component.
// components/tool-result.tsx
function ToolResult({ invocation }: { invocation: ToolInvocation }) {
// Tool is still being called — show loading state
if (invocation.state === 'call') {
return ;
}
// Tool returned a result — render the appropriate component
if (invocation.state === 'result') {
switch (invocation.toolName) {
case 'getWeather':
return ;
case 'searchProducts':
return ;
case 'getChart':
return ;
default:
return {JSON.stringify(invocation.result, null, 2)};
}
}
return null;
}
function ToolLoading({ name }: { name: string }) {
return (
);
}
Streaming Structured Objects
For streaming JSON objects (not just text) as they build up:
Server: streamObject
// app/api/analyze/route.ts
import { streamObject } from 'ai';
import { z } from 'zod';
const AnalysisSchema = z.object({
summary: z.string(),
sentiment: z.enum(['positive', 'negative', 'neutral']),
keyTopics: z.array(z.string()),
confidence: z.number(),
});
export async function POST(req: Request) {
const { text } = await req.json();
const result = streamObject({
model: anthropic('claude-sonnet-4-5-20250514'),
schema: AnalysisSchema,
prompt: `Analyze this text: ${text}`,
});
return result.toTextStreamResponse();
}
Client: useObject
'use client';
import { useObject } from '@ai-sdk/react';
function AnalysisPanel({ text }: { text: string }) {
const { object, isLoading } = useObject({
api: '/api/analyze',
schema: AnalysisSchema,
body: { text },
});
return (
{/* Fields appear progressively as AI generates them */}
{object?.summary && {object.summary}}
{object?.sentiment && }
{object?.keyTopics && (
{object.keyTopics.map(topic => {topic})}
)}
{object?.confidence != null && }
);
}
Generative UI within PPR
The T4 tier integrated with T0-T2. Static shell loads instantly, AI content fills in.
// app/dashboard/page.tsx
export default function AIAssistantPage() {
return (
<>
{/* T0: Static shell — instant from CDN */}
AI Assistant
{/* T1: Cached suggestions */}
{/* T4: AI chat — streams in */}
}>
{/* T1: Cached documentation sidebar */}
);
}
The user sees the full UI shell instantly, popular questions load from cache, and the AI chat is ready for interaction — all within 100ms. AI responses then stream in 1-3 seconds.
Multi-Stream Orchestration
Multiple independent AI streams in one UI:
function AIDashboard() {
const summary = useChat({ api: '/api/ai/summary', id: 'summary' });
const insights = useChat({ api: '/api/ai/insights', id: 'insights' });
const predictions = useChat({ api: '/api/ai/predictions', id: 'predictions' });
// Trigger all three on page load
useEffect(() => {
summary.append({ role: 'user', content: 'Summarize today' });
insights.append({ role: 'user', content: 'Key insights' });
predictions.append({ role: 'user', content: 'Predictions' });
}, []);
return (
);
}
Each panel updates independently as its stream progresses.
Error Handling & Cancellation
const { messages, error, reload, stop, isLoading } = useChat({
api: '/api/chat',
onError: (error) => {
console.error('Chat error:', error);
// Show toast notification
},
});
// Retry last message
{error && (
Retry
)}
// Cancel in-progress generation
{isLoading && (
Stop generating
)}
Key Rules
- Use AI SDK UI (stable), not AI SDK RSC (experimental) for production
- Server returns data, client renders components — clean separation of concerns
- Tool invocations are the bridge between AI decisions and React components
- Always provide loading states for tool invocations (skeleton matching final size)
- Wrap AI components in Suspense for PPR integration
- Use
stop()— users expect to cancel long generations - Stream objects when structure matters —
streamObject+useObjectfor dashboards, forms - Cache common AI queries — wrap with
use cachefor FAQ-style responses
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Leo-Atienza
- Source: Leo-Atienza/atlas-claude
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.