mirror of
https://github.com/coleam00/Archon.git
synced 2025-12-24 02:39:17 -05:00
refactor: Phase 2 Query Keys Standardization - Complete TanStack Query v5 patterns implementation (#692)
* refactor: complete Phase 2 Query Keys Standardization Standardize query keys across all features following vertical slice architecture, ensuring they mirror backend API structure exactly with no backward compatibility. Key Changes: - Refactor all query key factories to follow consistent patterns - Move progress feature from knowledge/progress to top-level /features/progress - Create shared query patterns for consistency (DISABLED_QUERY_KEY, STALE_TIMES) - Remove all hardcoded stale times and disabled keys - Update all imports after progress feature relocation Query Key Factories Standardized: - projectKeys: removed task-related keys (tasks, taskCounts) - taskKeys: added dual nature support (global via lists(), project-scoped via byProject()) - knowledgeKeys: removed redundant methods (details, summary) - progressKeys: new top-level feature with consistent factory - documentKeys: full factory pattern with versions support - mcpKeys: complete with health endpoint Shared Patterns Implementation: - STALE_TIMES: instant (0), realtime (3s), frequent (5s), normal (30s), rare (5m), static (∞) - DISABLED_QUERY_KEY: consistent disabled query pattern across all features - Removed unused createQueryOptions helper Testing: - Added comprehensive tests for progress hooks - Updated all test mocks to include new STALE_TIMES values - All 81 feature tests passing Documentation: - Created QUERY_PATTERNS.md guide for future implementations - Clear patterns, examples, and migration checklist Breaking Changes: - Progress imports moved from knowledge/progress to progress - Query key structure changes (cache will reset) - No backward compatibility maintained Co-Authored-By: Claude <noreply@anthropic.com> * fix: establish single source of truth for tags in metadata - Remove ambiguous top-level tags field from KnowledgeItem interface - Update all UI components to use metadata.tags exclusively - Fix mutations to correctly update tags in metadata object - Remove duplicate tags field from backend KnowledgeSummaryService - Fix test setup issue with QueryClient instance in knowledge tests - Add TODO comments for filter-blind optimistic updates (Phase 3) This eliminates the ambiguity identified in Phase 2 where both item.tags and metadata.tags existed, establishing metadata.tags as the single source of truth across the entire stack. * fix: comprehensive progress hooks improvements - Integrate useSmartPolling for all polling queries - Fix memory leaks from uncleaned timeouts - Replace string-based error checking with status codes - Remove TypeScript any usage with proper types - Fix unstable dependencies with sorted JSON serialization - Add staleTime to document queries for consistency * feat: implement flexible assignee system for dynamic agents - Changed assignee from restricted enum to flexible string type - Renamed "AI IDE Agent" to "Coding Agent" for clarity - Enhanced ComboBox with Radix UI best practices: - Full ARIA compliance (roles, labels, keyboard nav) - Performance optimizations (memoization, useCallback) - Improved UX (auto-scroll, keyboard shortcuts) - Fixed event bubbling preventing unintended modal opens - Updated MCP server docs to reflect flexible assignee capability - Removed unnecessary UI elements (arrows, helper text) - Styled ComboBox to match priority selector aesthetic This allows external MCP clients to create and assign custom sub-agents dynamically, supporting advanced agent orchestration workflows. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * fix: complete Phase 2 summariesPrefix usage for cache consistency - Fix all knowledgeKeys.summaries() calls to use summariesPrefix() for operations targeting multiple summary caches - Update cancelQueries, getQueriesData, setQueriesData, invalidateQueries, and refetchQueries calls - Fix critical cache invalidation bug where filtered summaries weren't being cleared - Update test expectations to match new factory patterns - Address CodeRabbit review feedback on cache stability issues This completes the Phase 2 Query Keys Standardization work documented in PRPs/local/frontend-state-management-refactor.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: update MCP task tools documentation for Coding Agent rename Update task assignee documentation from "AI IDE Agent" to "Coding Agent" to match frontend changes for consistency across the system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: implement assignee filtering in MCP find_tasks function Add missing implementation for filter_by="assignee" that was documented but not coded. The filter now properly passes the assignee parameter to the backend API, matching the existing pattern used for status filtering. Fixes documentation/implementation mismatch identified by CodeRabbit. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Phase 2 cleanup - address review comments and improve code quality Changes made: - Reduced smart polling interval from 60s to 5s for background tabs (better responsiveness) - Fixed cache coherence bug in knowledge queries (missing limit parameter) - Standardized "Coding Agent" naming (was inconsistently "AI IDE Agent") - Improved task queries with 2s polling, type safety, and proper invalidation - Enhanced combobox accessibility with proper ARIA attributes and IDs - Delegated useCrawlProgressPolling to useActiveOperations (removed duplication) - Added exact: true to progress query removals (prevents sibling removal) - Fixed invalid Tailwind class ml-4.5 to ml-4 All changes align with Phase 2 query key standardization goals and improve overall code quality, accessibility, and performance. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -109,7 +109,8 @@ GET /api/agent-chat/sessions/{id}/messages - Chat messages
|
||||
### Database Types (from backend)
|
||||
```typescript
|
||||
type DatabaseTaskStatus = 'todo' | 'doing' | 'review' | 'done';
|
||||
type Assignee = 'User' | 'Archon' | 'AI IDE Agent';
|
||||
type Assignee = string; // Flexible string to support any agent name
|
||||
// Common values: 'User', 'Archon', 'Coding Agent'
|
||||
```
|
||||
|
||||
### Request/Response Types
|
||||
|
||||
227
PRPs/ai_docs/QUERY_PATTERNS.md
Normal file
227
PRPs/ai_docs/QUERY_PATTERNS.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# TanStack Query Patterns Guide
|
||||
|
||||
This guide documents the standardized patterns for using TanStack Query v5 in the Archon frontend.
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Feature Ownership**: Each feature owns its query keys in `{feature}/hooks/use{Feature}Queries.ts`
|
||||
2. **Consistent Patterns**: Always use shared patterns from `shared/queryPatterns.ts`
|
||||
3. **No Hardcoded Values**: Never hardcode stale times or disabled keys
|
||||
4. **Mirror Backend API**: Query keys should exactly match backend API structure
|
||||
|
||||
## Query Key Factory Pattern
|
||||
|
||||
Every feature MUST implement a query key factory following this pattern:
|
||||
|
||||
```typescript
|
||||
// features/{feature}/hooks/use{Feature}Queries.ts
|
||||
export const featureKeys = {
|
||||
all: ["feature"] as const, // Base key for the domain
|
||||
lists: () => [...featureKeys.all, "list"] as const, // For list endpoints
|
||||
detail: (id: string) => [...featureKeys.all, "detail", id] as const, // For single item
|
||||
// Add more as needed following backend routes
|
||||
};
|
||||
```
|
||||
|
||||
### Examples from Codebase
|
||||
|
||||
```typescript
|
||||
// Projects - Simple hierarchy
|
||||
export const projectKeys = {
|
||||
all: ["projects"] as const,
|
||||
lists: () => [...projectKeys.all, "list"] as const,
|
||||
detail: (id: string) => [...projectKeys.all, "detail", id] as const,
|
||||
features: (id: string) => [...projectKeys.all, id, "features"] as const,
|
||||
};
|
||||
|
||||
// Tasks - Dual nature (global and project-scoped)
|
||||
export const taskKeys = {
|
||||
all: ["tasks"] as const,
|
||||
lists: () => [...taskKeys.all, "list"] as const, // /api/tasks
|
||||
detail: (id: string) => [...taskKeys.all, "detail", id] as const,
|
||||
byProject: (projectId: string) => ["projects", projectId, "tasks"] as const, // /api/projects/{id}/tasks
|
||||
counts: () => [...taskKeys.all, "counts"] as const,
|
||||
};
|
||||
```
|
||||
|
||||
## Shared Patterns Usage
|
||||
|
||||
### Import Required Patterns
|
||||
|
||||
```typescript
|
||||
import { DISABLED_QUERY_KEY, STALE_TIMES } from "@/features/shared/queryPatterns";
|
||||
```
|
||||
|
||||
### Disabled Queries
|
||||
|
||||
Always use `DISABLED_QUERY_KEY` when a query should not execute:
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT
|
||||
queryKey: projectId ? projectKeys.detail(projectId) : DISABLED_QUERY_KEY,
|
||||
|
||||
// ❌ WRONG - Don't create custom disabled keys
|
||||
queryKey: projectId ? projectKeys.detail(projectId) : ["projects-undefined"],
|
||||
```
|
||||
|
||||
### Stale Times
|
||||
|
||||
Always use `STALE_TIMES` constants for cache configuration:
|
||||
|
||||
```typescript
|
||||
// ✅ CORRECT
|
||||
staleTime: STALE_TIMES.normal, // 30 seconds
|
||||
staleTime: STALE_TIMES.frequent, // 5 seconds
|
||||
staleTime: STALE_TIMES.instant, // 0 - always fresh
|
||||
|
||||
// ❌ WRONG - Don't hardcode times
|
||||
staleTime: 30000,
|
||||
staleTime: 0,
|
||||
```
|
||||
|
||||
#### STALE_TIMES Reference
|
||||
|
||||
- `instant: 0` - Always fresh (real-time data like active progress)
|
||||
- `realtime: 3_000` - 3 seconds (near real-time updates)
|
||||
- `frequent: 5_000` - 5 seconds (frequently changing data)
|
||||
- `normal: 30_000` - 30 seconds (standard cache time)
|
||||
- `rare: 300_000` - 5 minutes (rarely changing config)
|
||||
- `static: Infinity` - Never stale (settings, auth)
|
||||
|
||||
## Complete Hook Pattern
|
||||
|
||||
```typescript
|
||||
export function useFeatureDetail(id: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: id ? featureKeys.detail(id) : DISABLED_QUERY_KEY,
|
||||
queryFn: () => id
|
||||
? featureService.getFeatureById(id)
|
||||
: Promise.reject("No ID provided"),
|
||||
enabled: !!id,
|
||||
staleTime: STALE_TIMES.normal,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Mutations with Optimistic Updates
|
||||
|
||||
```typescript
|
||||
export function useCreateFeature() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateFeatureRequest) => featureService.create(data),
|
||||
|
||||
onMutate: async (newData) => {
|
||||
// Cancel in-flight queries
|
||||
await queryClient.cancelQueries({ queryKey: featureKeys.lists() });
|
||||
|
||||
// Snapshot for rollback
|
||||
const previous = queryClient.getQueryData(featureKeys.lists());
|
||||
|
||||
// Optimistic update (use timestamp IDs for now - Phase 3 will use UUIDs)
|
||||
const tempId = `temp-${Date.now()}`;
|
||||
queryClient.setQueryData(featureKeys.lists(), (old: Feature[] = []) =>
|
||||
[...old, { ...newData, id: tempId }]
|
||||
);
|
||||
|
||||
return { previous, tempId };
|
||||
},
|
||||
|
||||
onError: (err, variables, context) => {
|
||||
// Rollback on error
|
||||
if (context?.previous) {
|
||||
queryClient.setQueryData(featureKeys.lists(), context.previous);
|
||||
}
|
||||
},
|
||||
|
||||
onSuccess: (data, variables, context) => {
|
||||
// Replace optimistic with real data
|
||||
queryClient.setQueryData(featureKeys.lists(), (old: Feature[] = []) =>
|
||||
old.map(item => item.id === context?.tempId ? data : item)
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Query Hooks
|
||||
|
||||
Always mock both services and shared patterns:
|
||||
|
||||
```typescript
|
||||
// Mock services
|
||||
vi.mock("../../services", () => ({
|
||||
featureService: {
|
||||
getList: vi.fn(),
|
||||
getById: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock shared patterns with ALL values
|
||||
vi.mock("../../../shared/queryPatterns", () => ({
|
||||
DISABLED_QUERY_KEY: ["disabled"] as const,
|
||||
STALE_TIMES: {
|
||||
instant: 0,
|
||||
realtime: 3_000,
|
||||
frequent: 5_000,
|
||||
normal: 30_000,
|
||||
rare: 300_000,
|
||||
static: Infinity,
|
||||
},
|
||||
}));
|
||||
```
|
||||
|
||||
## Vertical Slice Architecture
|
||||
|
||||
Each feature is self-contained:
|
||||
|
||||
```
|
||||
src/features/projects/
|
||||
├── components/ # UI components
|
||||
├── hooks/
|
||||
│ └── useProjectQueries.ts # Query hooks & keys
|
||||
├── services/
|
||||
│ └── projectService.ts # API calls
|
||||
└── types/
|
||||
└── index.ts # TypeScript types
|
||||
```
|
||||
|
||||
Sub-features (like tasks under projects) follow the same structure:
|
||||
|
||||
```
|
||||
src/features/projects/tasks/
|
||||
├── components/
|
||||
├── hooks/
|
||||
│ └── useTaskQueries.ts # Own query keys!
|
||||
├── services/
|
||||
└── types/
|
||||
```
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
When refactoring to these patterns:
|
||||
|
||||
- [ ] Create query key factory in `hooks/use{Feature}Queries.ts`
|
||||
- [ ] Import `DISABLED_QUERY_KEY` and `STALE_TIMES` from shared
|
||||
- [ ] Replace all hardcoded disabled keys with `DISABLED_QUERY_KEY`
|
||||
- [ ] Replace all hardcoded stale times with `STALE_TIMES` constants
|
||||
- [ ] Update all `queryKey` references to use factory
|
||||
- [ ] Update all `invalidateQueries` to use factory
|
||||
- [ ] Update all `setQueryData` to use factory
|
||||
- [ ] Add comprehensive tests for query keys
|
||||
- [ ] Remove any backward compatibility code
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
1. **Don't create centralized query keys** - Each feature owns its keys
|
||||
2. **Don't hardcode values** - Use shared constants
|
||||
3. **Don't mix concerns** - Tasks shouldn't import projectKeys
|
||||
4. **Don't skip mocking in tests** - Mock both services and patterns
|
||||
5. **Don't use inconsistent patterns** - Follow the established conventions
|
||||
|
||||
## Future Improvements (Phase 3+)
|
||||
|
||||
- Replace timestamp IDs (`temp-${Date.now()}`) with UUIDs
|
||||
- Add Server-Sent Events for real-time updates
|
||||
- Consider Zustand for complex client state
|
||||
Reference in New Issue
Block a user