feat: production readiness improvements

## Error Handling
- Add GlobalErrorBoundary with error classification and recovery
- Add custom error types (SecurityError, ConnectionError, TimeoutError)
- Fix ErrorAlert component syntax errors

## Offline Mode
- Add offlineStore for offline state management
- Implement message queue with localStorage persistence
- Add exponential backoff reconnection (1s→60s)
- Add OfflineIndicator component with status display
- Queue messages when offline, auto-retry on reconnect

## Security Hardening
- Add AES-256-GCM encryption for chat history storage
- Add secure API key storage with OS keychain integration
- Add security audit logging system
- Add XSS prevention and input validation utilities
- Add rate limiting and token generation helpers

## CI/CD (Gitea Actions)
- Add .gitea/workflows/ci.yml for continuous integration
- Add .gitea/workflows/release.yml for release automation
- Support Windows Tauri build and release

## UI Components
- Add LoadingSpinner, LoadingOverlay, LoadingDots components
- Add MessageSkeleton, ConversationListSkeleton skeletons
- Add EmptyMessages, EmptyConversations empty states
- Integrate loading states in ChatArea and ConversationList

## E2E Tests
- Fix WebSocket mock for streaming response tests
- Fix approval endpoint route matching
- Add store state exposure for testing
- All 19 core-features tests now passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-22 00:03:22 +08:00
parent ce562e8bfc
commit 185763868a
27 changed files with 5725 additions and 268 deletions

View File

@@ -68,8 +68,23 @@ export const storeInspectors = {
/**
* 获取持久化的 Chat Store 状态
* 优先从运行时获取,如果不可用则从 localStorage 获取
*/
async getChatState<T = unknown>(page: Page): Promise<T | null> {
// First try to get runtime state (more reliable for E2E tests)
const runtimeState = await page.evaluate(() => {
const stores = (window as any).__ZCLAW_STORES__;
if (stores && stores.chat) {
return stores.chat.getState() as T;
}
return null;
});
if (runtimeState) {
return runtimeState;
}
// Fallback to localStorage
return page.evaluate((key) => {
const stored = localStorage.getItem(key);
if (!stored) return null;