Provides TypeScript and React coding standards for this repository. Use when writing or modifying TypeScript code, creating React components, implementing MSAL authentication, or working with the frontend.
Goal: Write type-safe React components with proper MSAL integration
The frontend runs with Vite HMR. When you edit TypeScript/React code:
VS Code Tasks (use Run Task command or check terminal panel):
Frontend: React Vite - Runs npm run dev with HMR enabledNo restart needed - Just edit, save, and see changes instantly in the browser.
Testing changes: Use Playwright browser tools to:
Enable strict mode + explicit types (avoid any):
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
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>;
}
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;
}
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 IDUse useState (local) or Context API (shared):
const [messages, setMessages] = useState<Message[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
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]
);
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}`);
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
import.meta.env.* in functionsany typenpm install outside frontend/ (misses .npmrc config)useMemo| 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 |
| Component | Purpose |
|-----------|---------|
| AgentChat.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) |
Parser: frontend/src/utils/citationParser.ts
Handles Azure AI Agent citation formats:
【4:0†source】, 【13†myfile.pdf】[doc1], [doc2]Flow:
parseContentWithCitations() replaces placeholders with [N] markersMarkdown.tsx renders CitationMarker components for each [N]AssistantMessage.tsx renders 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 indexLimits: 5MB per file, max 5 files total
See: frontend/src/utils/fileAttachments.ts for validateImageFile() and validateFileCount()
File: frontend/src/services/chatService.ts
Key patterns:
Dispatch<AppAction> for state updatesAbortController for stream cancellation (cancelStream())retryWithBackoff() for resilient API calls (3 retries, 1s initial delay)parseSseLine() and splitSseBuffer() utilitiesMethods:
| 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 |
AppAction union in frontend/src/types/appState.tsfrontend/src/reducers/appReducer.ts (keep pure, no side effects)ChatService if network interaction neededAgentChat.tsx to dispatch actionsaria-busy attribute on messages container during streamingaria-label when icon-onlyaria-describedbynpx skills add microsoft-foundry/writing-typescript-code下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer