writing-typescript-code
npx skills add https://github.com/microsoft-foundry/foundry-agent-webapp --skill writing-typescript-code
Agent 安装分布
Skill 文档
TypeScript Coding Standards
Goal: Write type-safe React components with proper MSAL integration
Hot Module Replacement (HMR) Workflow
The frontend runs with Vite HMR. When you edit TypeScript/React code:
- Save the file – Vite instantly updates the browser (no refresh needed)
- Check the terminal – Look for HMR updates in the “Frontend: React Vite” terminal
- State is preserved – React state persists through most edits
VS Code Tasks (use Run Task command or check terminal panel):
Frontend: React Vite– Runsnpm run devwith HMR enabled- Logs are visible directly in VS Code terminal
No restart needed – Just edit, save, and see changes instantly in the browser.
Testing changes: Use Playwright browser tools to:
- Navigate to http://localhost:5173
- Check browser console logs for state transitions and errors
- Inspect network requests for API validation
TypeScript Config
Enable strict mode + explicit types (avoid any):
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
React Components
Use functional components + hooks + typed props:
interface MessageProps {
message: string;
sender: 'user' | 'agent';
}
function Message({ message, sender }: MessageProps) {
return <div className={`msg-${sender}`}>{message}</div>;
}
MSAL Pattern
Always: Try silent first, fallback to popup:
try {
const { accessToken } = await instance.acquireTokenSilent({
...tokenRequest,
account: accounts[0]
});
return accessToken;
} catch {
const { accessToken } = await instance.acquireTokenPopup(tokenRequest);
return accessToken;
}
Environment Variables
CRITICAL: Access at module level only (build-time replacement):
// â
Correct - module level
const clientId = import.meta.env.VITE_ENTRA_SPA_CLIENT_ID;
// â Wrong - inside function (won't work after build)
function getClientId() {
return import.meta.env.VITE_ENTRA_SPA_CLIENT_ID;
}
Available variables:
VITE_ENTRA_SPA_CLIENT_ID– Entra app client IDVITE_ENTRA_TENANT_ID– Azure tenant ID
State Management
Use useState (local) or Context API (shared):
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
Memoization Patterns
Use useMemo and useCallback for expensive computations and stable references:
// Memoize computed values
const isAuthenticated = useMemo(
() => accounts.length > 0,
[accounts.length]
);
// Memoize callbacks to prevent child re-renders
const getAccessToken = useCallback(async () => {
// ... token acquisition logic
}, [instance, accounts]);
// Return memoized object for stable reference
return useMemo(
() => ({ getAccessToken, isAuthenticated, user }),
[getAccessToken, isAuthenticated, user]
);
API Calls
Include Authorization header + use async/await:
const response = await fetch('/api/endpoint', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
npm Dependencies
frontend/.npmrc sets legacy-peer-deps=true automatically â no flag needed when running from frontend/.
Gotcha: legacy-peer-deps skips automatic peer dependency installation. If a package requires peer deps, add them explicitly to package.json.
Example: @lexical/yjs requires yjs as a peer dependency. Since peer deps aren’t auto-installed, yjs must be in package.json directly.
Before committing package changes, verify with:
npm ci # Fails if lock file is out of sync with package.json
Common Mistakes
- â Accessing
import.meta.env.*in functions - â Calling hooks conditionally or in loops
- â Using
anytype - â Storing tokens in component state
- â Running
npm installoutsidefrontend/(misses.npmrcconfig) - â Missing memoization in custom hooks (causes infinite re-renders)
- â Returning new objects from hooks without
useMemo
Project-Specific: Architecture
| Concern | Implementation |
|---|---|
| State Management | Centralized Context + useReducer (AppContext) with discriminated action union |
| Authentication | MSAL redirect flow; silent token refresh; useAuth hook |
| Chat Streaming | SSE in ChatService with abort controllers for cancellation |
| Accessibility | Live region (aria-live), aria labels, focus management |
| Logging | Dev-only diff-based logger |
Project-Specific: Key Components
| Component | Purpose |
|---|---|
AgentPreview.tsx |
Container wiring chat state to controlled ChatInterface |
ChatInterface.tsx |
Stateless controlled UI; renders messages, input, errors, BuiltWithBadge |
chat/AssistantMessage.tsx |
Memoized assistant message with streaming + citation footnotes |
chat/UserMessage.tsx |
Memoized user message with image thumbnail previews |
chat/ChatInput.tsx |
File uploads, character counter, cancel streaming button |
chat/CitationMarker.tsx |
Inline superscript citation badge with tooltip + click handler |
core/Markdown.tsx |
Renders markdown with inline citation markers via ContentWithCitations |
core/BuiltWithBadge.tsx |
“Built with Microsoft Foundry” link badge (centered under input) |
Project-Specific: Citation System
Parser: frontend/src/utils/citationParser.ts
Handles Azure AI Agent citation formats:
- Assistants/Responses API:
ã4:0â sourceã,ã13â myfile.pdfã - Azure OpenAI On Your Data:
[doc1],[doc2]
Flow:
parseContentWithCitations()replaces placeholders with[N]markersMarkdown.tsxrendersCitationMarkercomponents for each[N]- Clicking inline marker scrolls to footnote (with highlight animation) or opens URL
AssistantMessage.tsxrenders footnote list with icons by type (URI/file/document)
Key Types (frontend/src/types/chat.ts):
IAnnotation– Citation metadata (type, label, url, fileId, quote, textToReplace)IndexedCitation– Parsed citation with display index
Project-Specific: File Upload Validation
Limits: 5MB per file, max 5 files total
See: frontend/src/utils/fileAttachments.ts for validateImageFile() and validateFileCount()
Project-Specific: ChatService
File: frontend/src/services/chatService.ts
Key patterns:
- Class-based service with
Dispatch<AppAction>for state updates AbortControllerfor stream cancellation (cancelStream())retryWithBackoff()for resilient API calls (3 retries, 1s initial delay)- SSE parsing via
parseSseLine()andsplitSseBuffer()utilities - Duplicate chunk suppression to prevent UI flicker
Methods:
| Method | Purpose |
|---|---|
sendMessage() |
Orchestrates auth, file conversion, streaming |
cancelStream() |
Aborts active stream, dispatches CHAT_CANCEL_STREAM |
clearChat() |
Resets conversation state |
clearError() |
Clears error without affecting chat |
Project-Specific: Adding Features
- Extend state: Add discriminated action to
AppActionunion infrontend/src/types/appState.ts - Handle in reducer: Update
frontend/src/reducers/appReducer.ts(keep pure, no side effects) - Create service method: Add to
ChatServiceif network interaction needed - Wire container: Update
AgentPreview.tsxto dispatch actions - Update UI: Pass callbacks to controlled component
Project-Specific: Accessibility Checklist
- â Live region announces latest assistant message
- â
aria-busyattribute on messages container during streaming - â
Buttons have
aria-labelwhen icon-only - â Focus returns to input after sending
- â
Character counter linked via
aria-describedby
Related Skills
- implementing-chat-streaming – SSE streaming patterns and frontend state flow
- troubleshooting-authentication – MSAL popup issues and token debugging
- testing-with-playwright – Browser testing and accessibility validation