* 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>
6.6 KiB
6.6 KiB
TanStack Query Patterns Guide
This guide documents the standardized patterns for using TanStack Query v5 in the Archon frontend.
Core Principles
- Feature Ownership: Each feature owns its query keys in
{feature}/hooks/use{Feature}Queries.ts - Consistent Patterns: Always use shared patterns from
shared/queryPatterns.ts - No Hardcoded Values: Never hardcode stale times or disabled keys
- 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:
// 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
// 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
import { DISABLED_QUERY_KEY, STALE_TIMES } from "@/features/shared/queryPatterns";
Disabled Queries
Always use DISABLED_QUERY_KEY when a query should not execute:
// ✅ 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:
// ✅ 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
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
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:
// 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_KEYandSTALE_TIMESfrom shared - Replace all hardcoded disabled keys with
DISABLED_QUERY_KEY - Replace all hardcoded stale times with
STALE_TIMESconstants - Update all
queryKeyreferences to use factory - Update all
invalidateQueriesto use factory - Update all
setQueryDatato use factory - Add comprehensive tests for query keys
- Remove any backward compatibility code
Common Pitfalls to Avoid
- Don't create centralized query keys - Each feature owns its keys
- Don't hardcode values - Use shared constants
- Don't mix concerns - Tasks shouldn't import projectKeys
- Don't skip mocking in tests - Mock both services and patterns
- 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