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:
Wirasm
2025-09-18 11:05:03 +03:00
committed by GitHub
parent b383c8cbec
commit f4ad785439
44 changed files with 1344 additions and 492 deletions

View File

@@ -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

View 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