From 63a92cf7d75bd4fbc236816196d5c506f5c620c3 Mon Sep 17 00:00:00 2001
From: Wirasm <152263317+Wirasm@users.noreply.github.com>
Date: Mon, 22 Sep 2025 14:59:33 +0300
Subject: [PATCH] refactor: reorganize features/shared directory for better
maintainability (#730)
* refactor: reorganize features/shared directory structure
- Created organized subdirectories for better code organization:
- api/ - API clients and HTTP utilities (renamed apiWithEtag.ts to apiClient.ts)
- config/ - Configuration files (queryClient, queryPatterns)
- types/ - Shared type definitions (errors)
- utils/ - Pure utility functions (optimistic, clipboard)
- hooks/ - Shared React hooks (already existed)
- Updated all import paths across the codebase (~40+ files)
- Updated all AI documentation in PRPs/ai_docs/ to reflect new structure
- All tests passing, build successful, no functional changes
This improves maintainability and follows vertical slice architecture patterns.
Co-Authored-By: Claude
* fix: address PR review comments and code improvements
- Update imports to use @/features alias path for optimistic utils
- Fix optimistic upload item replacement by matching on source_id instead of id
- Clean up test suite naming and remove meta-terms from comments
- Only set Content-Type header on requests with body
- Add explicit TypeScript typing to useProjectFeatures hook
- Complete Phase 4 improvements with proper query typing
* fix: address additional PR review feedback
- Clear feature queries when deleting project to prevent cache memory leaks
- Update KnowledgeCard comments to follow documentation guidelines
- Add explanatory comment for accessibility pattern in KnowledgeCard
---------
Co-authored-by: Claude
---
PRPs/ai_docs/API_NAMING_CONVENTIONS.md | 2 +-
PRPs/ai_docs/ARCHITECTURE.md | 6 +-
PRPs/ai_docs/DATA_FETCHING_ARCHITECTURE.md | 10 +-
PRPs/ai_docs/ETAG_IMPLEMENTATION.md | 10 +-
PRPs/ai_docs/QUERY_PATTERNS.md | 8 +-
PRPs/ai_docs/optimistic_updates.md | 6 +-
archon-ui-main/src/App.tsx | 2 +-
.../layout/hooks/useBackendHealth.ts | 4 +-
.../knowledge/components/KnowledgeCard.tsx | 7 +-
.../knowledge/hooks/useKnowledgeQueries.ts | 17 +-
.../components/KnowledgeInspector.tsx | 2 +-
.../inspector/hooks/useInspectorPagination.ts | 2 +-
.../knowledge/services/knowledgeService.ts | 4 +-
.../utils/tests/providerErrorHandler.test.ts | 206 +++++++++---------
.../knowledge/views/KnowledgeView.tsx | 2 +-
.../mcp/components/McpConfigSection.tsx | 5 +-
.../src/features/mcp/hooks/useMcpQueries.ts | 2 +-
.../src/features/mcp/services/mcpApi.ts | 2 +-
.../hooks/tests/useProgressQueries.test.ts | 2 +-
.../progress/hooks/useProgressQueries.ts | 4 +-
.../progress/services/progressService.ts | 2 +-
.../projects/components/ProjectCard.tsx | 2 +-
.../documents/components/DocumentCard.tsx | 2 +-
.../documents/hooks/useDocumentQueries.ts | 2 +-
.../projects/hooks/useProjectQueries.ts | 14 +-
.../projects/services/projectService.ts | 4 +-
.../projects/tasks/components/TaskCard.tsx | 2 +-
.../projects/tasks/hooks/useTaskQueries.ts | 8 +-
.../projects/tasks/services/taskService.ts | 4 +-
.../tasks/services/tests/taskService.test.ts | 4 +-
.../components/MigrationStatusCard.tsx | 6 +-
.../components/PendingMigrationsModal.tsx | 17 +-
.../migrations/hooks/useMigrationQueries.ts | 2 +-
.../migrations/services/migrationService.ts | 2 +-
.../version/components/UpdateBanner.tsx | 3 +-
.../version/components/VersionStatusCard.tsx | 3 +-
.../version/hooks/useVersionQueries.ts | 2 +-
.../version/services/versionService.ts | 2 +-
.../{apiWithEtag.ts => api/apiClient.ts} | 23 +-
.../tests/apiClient.test.ts} | 18 +-
.../shared/{ => config}/queryClient.ts | 0
.../shared/{ => config}/queryPatterns.ts | 0
.../src/features/shared/hooks/index.ts | 2 +-
.../src/features/shared/hooks/useToast.ts | 2 +-
.../src/features/shared/{ => types}/errors.ts | 0
.../features/shared/{ => utils}/optimistic.ts | 0
.../{ => utils/tests}/optimistic.test.ts | 14 +-
.../src/features/testing/test-utils.tsx | 2 +-
48 files changed, 230 insertions(+), 215 deletions(-)
rename archon-ui-main/src/features/shared/{apiWithEtag.ts => api/apiClient.ts} (83%)
rename archon-ui-main/src/features/shared/{apiWithEtag.test.ts => api/tests/apiClient.test.ts} (96%)
rename archon-ui-main/src/features/shared/{ => config}/queryClient.ts (100%)
rename archon-ui-main/src/features/shared/{ => config}/queryPatterns.ts (100%)
rename archon-ui-main/src/features/shared/{ => types}/errors.ts (100%)
rename archon-ui-main/src/features/shared/{ => utils}/optimistic.ts (100%)
rename archon-ui-main/src/features/shared/{ => utils/tests}/optimistic.test.ts (97%)
diff --git a/PRPs/ai_docs/API_NAMING_CONVENTIONS.md b/PRPs/ai_docs/API_NAMING_CONVENTIONS.md
index 5688912b..2135bc8d 100644
--- a/PRPs/ai_docs/API_NAMING_CONVENTIONS.md
+++ b/PRPs/ai_docs/API_NAMING_CONVENTIONS.md
@@ -198,7 +198,7 @@ Database values used directly - no mapping layers:
- Operation statuses: `"pending"`, `"processing"`, `"completed"`, `"failed"`
### Time Constants
-**Location**: `archon-ui-main/src/features/shared/queryPatterns.ts`
+**Location**: `archon-ui-main/src/features/shared/config/queryPatterns.ts`
- `STALE_TIMES.instant` - 0ms
- `STALE_TIMES.realtime` - 3 seconds
- `STALE_TIMES.frequent` - 5 seconds
diff --git a/PRPs/ai_docs/ARCHITECTURE.md b/PRPs/ai_docs/ARCHITECTURE.md
index a5c0ae7a..eb3a7f81 100644
--- a/PRPs/ai_docs/ARCHITECTURE.md
+++ b/PRPs/ai_docs/ARCHITECTURE.md
@@ -88,8 +88,8 @@ Pattern: `{METHOD} /api/{resource}/{id?}/{sub-resource?}`
### Data Fetching
**Core**: TanStack Query v5
-**Configuration**: `archon-ui-main/src/features/shared/queryClient.ts`
-**Patterns**: `archon-ui-main/src/features/shared/queryPatterns.ts`
+**Configuration**: `archon-ui-main/src/features/shared/config/queryClient.ts`
+**Patterns**: `archon-ui-main/src/features/shared/config/queryPatterns.ts`
### State Management
- **Server State**: TanStack Query
@@ -139,7 +139,7 @@ TanStack Query is the single source of truth. No separate state management neede
No translation layers. Database values (e.g., `"todo"`, `"doing"`) used directly in UI.
### Browser-Native Caching
-ETags handled by browser, not JavaScript. See `archon-ui-main/src/features/shared/apiWithEtag.ts`.
+ETags handled by browser, not JavaScript. See `archon-ui-main/src/features/shared/api/apiClient.ts`.
## Deployment
diff --git a/PRPs/ai_docs/DATA_FETCHING_ARCHITECTURE.md b/PRPs/ai_docs/DATA_FETCHING_ARCHITECTURE.md
index d8a9822b..8d1bbb62 100644
--- a/PRPs/ai_docs/DATA_FETCHING_ARCHITECTURE.md
+++ b/PRPs/ai_docs/DATA_FETCHING_ARCHITECTURE.md
@@ -8,7 +8,7 @@ Archon uses **TanStack Query v5** for all data fetching, caching, and synchroniz
### 1. Query Client Configuration
-**Location**: `archon-ui-main/src/features/shared/queryClient.ts`
+**Location**: `archon-ui-main/src/features/shared/config/queryClient.ts`
Centralized QueryClient with:
@@ -30,7 +30,7 @@ Visibility-aware polling that:
### 3. Query Patterns
-**Location**: `archon-ui-main/src/features/shared/queryPatterns.ts`
+**Location**: `archon-ui-main/src/features/shared/config/queryPatterns.ts`
Shared constants:
@@ -64,7 +64,7 @@ Standard pattern across all features:
### ETag Support
-**Location**: `archon-ui-main/src/features/shared/apiWithEtag.ts`
+**Location**: `archon-ui-main/src/features/shared/api/apiClient.ts`
ETag implementation:
@@ -83,7 +83,7 @@ Backend endpoints follow RESTful patterns:
## Optimistic Updates
-**Utilities**: `archon-ui-main/src/features/shared/optimistic.ts`
+**Utilities**: `archon-ui-main/src/features/shared/utils/optimistic.ts`
All mutations use nanoid-based optimistic updates:
@@ -105,7 +105,7 @@ Polling intervals are defined in each feature's query hooks. See actual implemen
- **Progress**: `archon-ui-main/src/features/progress/hooks/useProgressQueries.ts`
- **MCP**: `archon-ui-main/src/features/mcp/hooks/useMcpQueries.ts`
-Standard intervals from `archon-ui-main/src/features/shared/queryPatterns.ts`:
+Standard intervals from `archon-ui-main/src/features/shared/config/queryPatterns.ts`:
- `STALE_TIMES.instant`: 0ms (always fresh)
- `STALE_TIMES.frequent`: 5 seconds (frequently changing data)
- `STALE_TIMES.normal`: 30 seconds (standard cache)
diff --git a/PRPs/ai_docs/ETAG_IMPLEMENTATION.md b/PRPs/ai_docs/ETAG_IMPLEMENTATION.md
index 70e4ce63..8560dbb5 100644
--- a/PRPs/ai_docs/ETAG_IMPLEMENTATION.md
+++ b/PRPs/ai_docs/ETAG_IMPLEMENTATION.md
@@ -17,7 +17,7 @@ The backend generates ETags for API responses:
- Returns `304 Not Modified` when ETags match
### Frontend Handling
-**Location**: `archon-ui-main/src/features/shared/apiWithEtag.ts`
+**Location**: `archon-ui-main/src/features/shared/api/apiClient.ts`
The frontend relies on browser-native HTTP caching:
- Browser automatically sends `If-None-Match` headers with cached ETags
@@ -28,7 +28,7 @@ The frontend relies on browser-native HTTP caching:
#### Browser vs Non-Browser Behavior
- **Standard Browsers**: Per the Fetch spec, a 304 response freshens the HTTP cache and returns the cached body to JavaScript
- **Non-Browser Runtimes** (React Native, custom fetch): May surface 304 with empty body to JavaScript
-- **Client Fallback**: The `apiWithEtag.ts` implementation handles both scenarios, ensuring consistent behavior across environments
+- **Client Fallback**: The `apiClient.ts` implementation handles both scenarios, ensuring consistent behavior across environments
## Implementation Details
@@ -81,8 +81,8 @@ Unlike previous implementations, the current approach:
### Configuration
Cache behavior is controlled through TanStack Query's `staleTime`:
-- See `archon-ui-main/src/features/shared/queryPatterns.ts` for standard times
-- See `archon-ui-main/src/features/shared/queryClient.ts` for global configuration
+- See `archon-ui-main/src/features/shared/config/queryPatterns.ts` for standard times
+- See `archon-ui-main/src/features/shared/config/queryClient.ts` for global configuration
## Performance Benefits
@@ -100,7 +100,7 @@ Cache behavior is controlled through TanStack Query's `staleTime`:
### Core Implementation
- **Backend Utilities**: `python/src/server/utils/etag_utils.py`
-- **Frontend Client**: `archon-ui-main/src/features/shared/apiWithEtag.ts`
+- **Frontend Client**: `archon-ui-main/src/features/shared/api/apiClient.ts`
- **Tests**: `python/tests/server/utils/test_etag_utils.py`
### Usage Examples
diff --git a/PRPs/ai_docs/QUERY_PATTERNS.md b/PRPs/ai_docs/QUERY_PATTERNS.md
index 3c3204db..499daa36 100644
--- a/PRPs/ai_docs/QUERY_PATTERNS.md
+++ b/PRPs/ai_docs/QUERY_PATTERNS.md
@@ -5,7 +5,7 @@ This guide documents the standardized patterns for using TanStack Query v5 in th
## 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`
+2. **Consistent Patterns**: Always use shared patterns from `shared/config/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
@@ -49,7 +49,7 @@ export const taskKeys = {
### Import Required Patterns
```typescript
-import { DISABLED_QUERY_KEY, STALE_TIMES } from "@/features/shared/queryPatterns";
+import { DISABLED_QUERY_KEY, STALE_TIMES } from "@/features/shared/config/queryPatterns";
```
### Disabled Queries
@@ -106,7 +106,7 @@ export function useFeatureDetail(id: string | undefined) {
## Mutations with Optimistic Updates
```typescript
-import { createOptimisticEntity, replaceOptimisticEntity } from "@/features/shared/optimistic";
+import { createOptimisticEntity, replaceOptimisticEntity } from "@/features/shared/utils/optimistic";
export function useCreateFeature() {
const queryClient = useQueryClient();
@@ -161,7 +161,7 @@ vi.mock("../../services", () => ({
}));
// Mock shared patterns with ALL values
-vi.mock("../../../shared/queryPatterns", () => ({
+vi.mock("../../../shared/config/queryPatterns", () => ({
DISABLED_QUERY_KEY: ["disabled"] as const,
STALE_TIMES: {
instant: 0,
diff --git a/PRPs/ai_docs/optimistic_updates.md b/PRPs/ai_docs/optimistic_updates.md
index 7be11ea6..219b7866 100644
--- a/PRPs/ai_docs/optimistic_updates.md
+++ b/PRPs/ai_docs/optimistic_updates.md
@@ -3,7 +3,7 @@
## Core Architecture
### Shared Utilities Module
-**Location**: `src/features/shared/optimistic.ts`
+**Location**: `src/features/shared/utils/optimistic.ts`
Provides type-safe utilities for managing optimistic state across all features:
- `createOptimisticId()` - Generates stable UUIDs using nanoid
@@ -73,13 +73,13 @@ Reusable component showing:
- Uses `createOptimisticId()` directly for progress tracking
### Toasts
-- **Location**: `src/features/ui/hooks/useToast.ts:43`
+- **Location**: `src/features/shared/hooks/useToast.ts:43`
- Uses `createOptimisticId()` for unique toast IDs
## Testing
### Unit Tests
-**Location**: `src/features/shared/optimistic.test.ts`
+**Location**: `src/features/shared/utils/tests/optimistic.test.ts`
Covers all utility functions with 8 test cases:
- ID uniqueness and format validation
diff --git a/archon-ui-main/src/App.tsx b/archon-ui-main/src/App.tsx
index 1d4e22d3..ea2539cc 100644
--- a/archon-ui-main/src/App.tsx
+++ b/archon-ui-main/src/App.tsx
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
-import { queryClient } from './features/shared/queryClient';
+import { queryClient } from './features/shared/config/queryClient';
import { KnowledgeBasePage } from './pages/KnowledgeBasePage';
import { SettingsPage } from './pages/SettingsPage';
import { MCPPage } from './pages/MCPPage';
diff --git a/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts b/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts
index 626d23b6..59e9ccfa 100644
--- a/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts
+++ b/archon-ui-main/src/components/layout/hooks/useBackendHealth.ts
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
-import { callAPIWithETag } from "../../../features/shared/apiWithEtag";
-import { createRetryLogic, STALE_TIMES } from "../../../features/shared/queryPatterns";
+import { callAPIWithETag } from "../../../features/shared/api/apiClient";
+import { createRetryLogic, STALE_TIMES } from "../../../features/shared/config/queryPatterns";
import type { HealthResponse } from "../types";
/**
diff --git a/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx b/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx
index bb49edd9..05c882de 100644
--- a/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx
+++ b/archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx
@@ -1,6 +1,6 @@
/**
- * Enhanced Knowledge Card Component
- * Individual knowledge item card with excellent UX and inline progress
+ * Knowledge Card component
+ * Displays a knowledge item with inline progress and status UI
* Following the pattern from ProjectCard
*/
@@ -10,7 +10,7 @@ import { Clock, Code, ExternalLink, File, FileText, Globe } from "lucide-react";
import { useState } from "react";
import { KnowledgeCardProgress } from "../../progress/components/KnowledgeCardProgress";
import type { ActiveOperation } from "../../progress/types";
-import { isOptimistic } from "../../shared/optimistic";
+import { isOptimistic } from "@/features/shared/utils/optimistic";
import { StatPill } from "../../ui/primitives";
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
import { cn } from "../../ui/primitives/styles";
@@ -144,6 +144,7 @@ export const KnowledgeCard: React.FC = ({
};
return (
+ // biome-ignore lint/a11y/useSemanticElements: Card contains nested interactive elements (buttons, links) - using div to avoid invalid HTML nesting
);
- const tempItemId = optimisticItem.id;
// Update all summaries caches with optimistic data, respecting each cache's filter
const entries = queryClient.getQueriesData({
@@ -229,7 +228,7 @@ export function useCrawlUrl() {
});
// Return context for rollback and replacement
- return { previousSummaries, previousOperations, tempProgressId, tempItemId };
+ return { previousSummaries, previousOperations, tempProgressId };
},
onSuccess: (response, _variables, context) => {
// Replace temporary IDs with real ones from the server
@@ -313,7 +312,6 @@ export function useUploadDocument() {
previousSummaries?: Array<[readonly unknown[], KnowledgeItemsResponse | undefined]>;
previousOperations?: ActiveOperationsResponse;
tempProgressId: string;
- tempItemId: string;
}
>({
mutationFn: ({ file, metadata }: { file: File; metadata: UploadMetadata }) =>
@@ -352,7 +350,6 @@ export function useUploadDocument() {
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
} as Omit);
- const tempItemId = optimisticItem.id;
// Respect each cache's filter (knowledge_type, tags, etc.)
const entries = queryClient.getQueriesData({
@@ -410,7 +407,7 @@ export function useUploadDocument() {
};
});
- return { previousSummaries, previousOperations, tempProgressId, tempItemId };
+ return { previousSummaries, previousOperations, tempProgressId };
},
onSuccess: (response, _variables, context) => {
// Replace temporary IDs with real ones from the server
@@ -421,7 +418,7 @@ export function useUploadDocument() {
return {
...old,
items: old.items.map((item) => {
- if (item.id === context.tempItemId) {
+ if (item.source_id === context.tempProgressId) {
return {
...item,
source_id: response.progressId,
diff --git a/archon-ui-main/src/features/knowledge/inspector/components/KnowledgeInspector.tsx b/archon-ui-main/src/features/knowledge/inspector/components/KnowledgeInspector.tsx
index 69e8f050..334d4567 100644
--- a/archon-ui-main/src/features/knowledge/inspector/components/KnowledgeInspector.tsx
+++ b/archon-ui-main/src/features/knowledge/inspector/components/KnowledgeInspector.tsx
@@ -4,13 +4,13 @@
*/
import { useCallback, useEffect, useState } from "react";
+import { copyToClipboard } from "../../../shared/utils/clipboard";
import { InspectorDialog, InspectorDialogContent, InspectorDialogTitle } from "../../../ui/primitives";
import type { CodeExample, DocumentChunk, InspectorSelectedItem, KnowledgeItem } from "../../types";
import { useInspectorPagination } from "../hooks/useInspectorPagination";
import { ContentViewer } from "./ContentViewer";
import { InspectorHeader } from "./InspectorHeader";
import { InspectorSidebar } from "./InspectorSidebar";
-import { copyToClipboard } from "../../../shared/utils/clipboard";
interface KnowledgeInspectorProps {
item: KnowledgeItem;
diff --git a/archon-ui-main/src/features/knowledge/inspector/hooks/useInspectorPagination.ts b/archon-ui-main/src/features/knowledge/inspector/hooks/useInspectorPagination.ts
index 613aa19d..a1f286d5 100644
--- a/archon-ui-main/src/features/knowledge/inspector/hooks/useInspectorPagination.ts
+++ b/archon-ui-main/src/features/knowledge/inspector/hooks/useInspectorPagination.ts
@@ -5,7 +5,7 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { useMemo } from "react";
-import { STALE_TIMES } from "@/features/shared/queryPatterns";
+import { STALE_TIMES } from "@/features/shared/config/queryPatterns";
import { knowledgeKeys } from "../../hooks/useKnowledgeQueries";
import { knowledgeService } from "../../services";
import type { ChunksResponse, CodeExample, CodeExamplesResponse, DocumentChunk } from "../../types";
diff --git a/archon-ui-main/src/features/knowledge/services/knowledgeService.ts b/archon-ui-main/src/features/knowledge/services/knowledgeService.ts
index b9d6af06..cfab3f7f 100644
--- a/archon-ui-main/src/features/knowledge/services/knowledgeService.ts
+++ b/archon-ui-main/src/features/knowledge/services/knowledgeService.ts
@@ -3,8 +3,8 @@
* Handles all knowledge-related API operations using TanStack Query patterns
*/
-import { callAPIWithETag } from "../../shared/apiWithEtag";
-import { APIServiceError } from "../../shared/errors";
+import { callAPIWithETag } from "../../shared/api/apiClient";
+import { APIServiceError } from "../../shared/types/errors";
import type {
ChunksResponse,
CodeExamplesResponse,
diff --git a/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts b/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts
index 193e2444..9ddf380a 100644
--- a/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts
+++ b/archon-ui-main/src/features/knowledge/utils/tests/providerErrorHandler.test.ts
@@ -1,70 +1,70 @@
-import { describe, it, expect } from 'vitest';
-import { parseProviderError, getProviderErrorMessage, type ProviderError } from '../providerErrorHandler';
+import { describe, expect, it } from "vitest";
+import { getProviderErrorMessage, type ProviderError, parseProviderError } from "../providerErrorHandler";
-describe('providerErrorHandler', () => {
- describe('parseProviderError', () => {
- it('should handle basic Error objects', () => {
- const error = new Error('Basic error message');
+describe("providerErrorHandler", () => {
+ describe("parseProviderError", () => {
+ it("should handle basic Error objects", () => {
+ const error = new Error("Basic error message");
const result = parseProviderError(error);
- expect(result.message).toBe('Basic error message');
+ expect(result.message).toBe("Basic error message");
expect(result.isProviderError).toBeUndefined();
});
- it('should handle errors with statusCode property', () => {
- const error = { statusCode: 401, message: 'Unauthorized' };
+ it("should handle errors with statusCode property", () => {
+ const error = { statusCode: 401, message: "Unauthorized" };
const result = parseProviderError(error);
expect(result.statusCode).toBe(401);
- expect(result.message).toBe('Unauthorized');
+ expect(result.message).toBe("Unauthorized");
});
- it('should handle errors with status property', () => {
- const error = { status: 429, message: 'Rate limited' };
+ it("should handle errors with status property", () => {
+ const error = { status: 429, message: "Rate limited" };
const result = parseProviderError(error);
expect(result.statusCode).toBe(429);
- expect(result.message).toBe('Rate limited');
+ expect(result.message).toBe("Rate limited");
});
- it('should prioritize statusCode over status when both are present', () => {
- const error = { statusCode: 401, status: 429, message: 'Auth error' };
+ it("should prioritize statusCode over status when both are present", () => {
+ const error = { statusCode: 401, status: 429, message: "Auth error" };
const result = parseProviderError(error);
expect(result.statusCode).toBe(401);
});
- it('should parse structured provider errors from backend', () => {
+ it("should parse structured provider errors from backend", () => {
const error = {
message: JSON.stringify({
detail: {
- error_type: 'authentication_failed',
- provider: 'OpenAI',
- message: 'Invalid API key'
- }
- })
+ error_type: "authentication_failed",
+ provider: "OpenAI",
+ message: "Invalid API key",
+ },
+ }),
};
const result = parseProviderError(error);
expect(result.isProviderError).toBe(true);
- expect(result.provider).toBe('OpenAI');
- expect(result.errorType).toBe('authentication_failed');
- expect(result.message).toBe('Invalid API key');
+ expect(result.provider).toBe("OpenAI");
+ expect(result.errorType).toBe("authentication_failed");
+ expect(result.message).toBe("Invalid API key");
});
- it('should handle malformed JSON in message gracefully', () => {
+ it("should handle malformed JSON in message gracefully", () => {
const error = {
- message: 'invalid json { detail'
+ message: "invalid json { detail",
};
const result = parseProviderError(error);
expect(result.isProviderError).toBeUndefined();
- expect(result.message).toBe('invalid json { detail');
+ expect(result.message).toBe("invalid json { detail");
});
- it('should handle null and undefined inputs safely', () => {
+ it("should handle null and undefined inputs safely", () => {
expect(() => parseProviderError(null)).not.toThrow();
expect(() => parseProviderError(undefined)).not.toThrow();
@@ -75,7 +75,7 @@ describe('providerErrorHandler', () => {
expect(undefinedResult).toBeDefined();
});
- it('should handle empty objects', () => {
+ it("should handle empty objects", () => {
const result = parseProviderError({});
expect(result).toBeDefined();
@@ -83,171 +83,171 @@ describe('providerErrorHandler', () => {
expect(result.isProviderError).toBeUndefined();
});
- it('should handle primitive values', () => {
- expect(() => parseProviderError('string error')).not.toThrow();
+ it("should handle primitive values", () => {
+ expect(() => parseProviderError("string error")).not.toThrow();
expect(() => parseProviderError(42)).not.toThrow();
expect(() => parseProviderError(true)).not.toThrow();
});
- it('should handle structured errors without provider field', () => {
+ it("should handle structured errors without provider field", () => {
const error = {
message: JSON.stringify({
detail: {
- error_type: 'quota_exhausted',
- message: 'Usage limit exceeded'
- }
- })
+ error_type: "quota_exhausted",
+ message: "Usage limit exceeded",
+ },
+ }),
};
const result = parseProviderError(error);
expect(result.isProviderError).toBe(true);
- expect(result.provider).toBe('LLM'); // Default fallback
- expect(result.errorType).toBe('quota_exhausted');
- expect(result.message).toBe('Usage limit exceeded');
+ expect(result.provider).toBe("LLM"); // Default fallback
+ expect(result.errorType).toBe("quota_exhausted");
+ expect(result.message).toBe("Usage limit exceeded");
});
- it('should handle partial structured errors', () => {
+ it("should handle partial structured errors", () => {
const error = {
message: JSON.stringify({
detail: {
- error_type: 'rate_limit'
+ error_type: "rate_limit",
// Missing message field
- }
- })
+ },
+ }),
};
const result = parseProviderError(error);
expect(result.isProviderError).toBe(true);
- expect(result.errorType).toBe('rate_limit');
+ expect(result.errorType).toBe("rate_limit");
expect(result.message).toBe(error.message); // Falls back to original message
});
});
- describe('getProviderErrorMessage', () => {
- it('should return user-friendly message for authentication_failed', () => {
+ describe("getProviderErrorMessage", () => {
+ it("should return user-friendly message for authentication_failed", () => {
const error: ProviderError = {
- name: 'Error',
- message: 'Auth failed',
+ name: "Error",
+ message: "Auth failed",
isProviderError: true,
- provider: 'OpenAI',
- errorType: 'authentication_failed'
+ provider: "OpenAI",
+ errorType: "authentication_failed",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('Please verify your OpenAI API key in Settings.');
+ expect(result).toBe("Please verify your OpenAI API key in Settings.");
});
- it('should return user-friendly message for quota_exhausted', () => {
+ it("should return user-friendly message for quota_exhausted", () => {
const error: ProviderError = {
- name: 'Error',
- message: 'Quota exceeded',
+ name: "Error",
+ message: "Quota exceeded",
isProviderError: true,
- provider: 'Google AI',
- errorType: 'quota_exhausted'
+ provider: "Google AI",
+ errorType: "quota_exhausted",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('Google AI quota exhausted. Please check your billing settings.');
+ expect(result).toBe("Google AI quota exhausted. Please check your billing settings.");
});
- it('should return user-friendly message for rate_limit', () => {
+ it("should return user-friendly message for rate_limit", () => {
const error: ProviderError = {
- name: 'Error',
- message: 'Rate limited',
+ name: "Error",
+ message: "Rate limited",
isProviderError: true,
- provider: 'Anthropic',
- errorType: 'rate_limit'
+ provider: "Anthropic",
+ errorType: "rate_limit",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('Anthropic rate limit exceeded. Please wait and try again.');
+ expect(result).toBe("Anthropic rate limit exceeded. Please wait and try again.");
});
- it('should return generic provider message for unknown error types', () => {
+ it("should return generic provider message for unknown error types", () => {
const error: ProviderError = {
- name: 'Error',
- message: 'Unknown error',
+ name: "Error",
+ message: "Unknown error",
isProviderError: true,
- provider: 'OpenAI',
- errorType: 'unknown_error'
+ provider: "OpenAI",
+ errorType: "unknown_error",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('OpenAI API error. Please check your configuration.');
+ expect(result).toBe("OpenAI API error. Please check your configuration.");
});
- it('should use default provider when provider is missing', () => {
+ it("should use default provider when provider is missing", () => {
const error: ProviderError = {
- name: 'Error',
- message: 'Auth failed',
+ name: "Error",
+ message: "Auth failed",
isProviderError: true,
- errorType: 'authentication_failed'
+ errorType: "authentication_failed",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('Please verify your LLM API key in Settings.');
+ expect(result).toBe("Please verify your LLM API key in Settings.");
});
- it('should handle 401 status code for non-provider errors', () => {
- const error = { statusCode: 401, message: 'Unauthorized' };
+ it("should handle 401 status code for non-provider errors", () => {
+ const error = { statusCode: 401, message: "Unauthorized" };
const result = getProviderErrorMessage(error);
- expect(result).toBe('Please verify your API key in Settings.');
+ expect(result).toBe("Please verify your API key in Settings.");
});
- it('should return original message for non-provider errors', () => {
- const error = new Error('Network connection failed');
+ it("should return original message for non-provider errors", () => {
+ const error = new Error("Network connection failed");
const result = getProviderErrorMessage(error);
- expect(result).toBe('Network connection failed');
+ expect(result).toBe("Network connection failed");
});
- it('should return default message when no message is available', () => {
+ it("should return default message when no message is available", () => {
const error = {};
const result = getProviderErrorMessage(error);
- expect(result).toBe('An error occurred.');
+ expect(result).toBe("An error occurred.");
});
- it('should handle complex error objects with structured backend response', () => {
+ it("should handle complex error objects with structured backend response", () => {
const backendError = {
statusCode: 400,
message: JSON.stringify({
detail: {
- error_type: 'authentication_failed',
- provider: 'OpenAI',
- message: 'API key invalid or expired'
- }
- })
+ error_type: "authentication_failed",
+ provider: "OpenAI",
+ message: "API key invalid or expired",
+ },
+ }),
};
const result = getProviderErrorMessage(backendError);
- expect(result).toBe('Please verify your OpenAI API key in Settings.');
+ expect(result).toBe("Please verify your OpenAI API key in Settings.");
});
it('should handle edge case: message contains "detail" but is not JSON', () => {
const error = {
- message: 'Error detail: something went wrong'
+ message: "Error detail: something went wrong",
};
const result = getProviderErrorMessage(error);
- expect(result).toBe('Error detail: something went wrong');
+ expect(result).toBe("Error detail: something went wrong");
});
- it('should handle null and undefined gracefully', () => {
- expect(getProviderErrorMessage(null)).toBe('An error occurred.');
- expect(getProviderErrorMessage(undefined)).toBe('An error occurred.');
+ it("should handle null and undefined gracefully", () => {
+ expect(getProviderErrorMessage(null)).toBe("An error occurred.");
+ expect(getProviderErrorMessage(undefined)).toBe("An error occurred.");
});
});
- describe('TypeScript strict mode compliance', () => {
- it('should handle type-safe property access', () => {
+ describe("TypeScript strict mode compliance", () => {
+ it("should handle type-safe property access", () => {
// Test that our type guards work properly
const errorWithStatus = { statusCode: 500 };
- const errorWithMessage = { message: 'test' };
- const errorWithBoth = { statusCode: 401, message: 'unauthorized' };
+ const errorWithMessage = { message: "test" };
+ const errorWithBoth = { statusCode: 401, message: "unauthorized" };
// These should not throw TypeScript errors and should work correctly
expect(() => parseProviderError(errorWithStatus)).not.toThrow();
@@ -259,13 +259,13 @@ describe('providerErrorHandler', () => {
const result3 = parseProviderError(errorWithBoth);
expect(result1.statusCode).toBe(500);
- expect(result2.message).toBe('test');
+ expect(result2.message).toBe("test");
expect(result3.statusCode).toBe(401);
- expect(result3.message).toBe('unauthorized');
+ expect(result3.message).toBe("unauthorized");
});
- it('should handle objects without expected properties safely', () => {
- const objectWithoutStatus = { someOtherProperty: 'value' };
+ it("should handle objects without expected properties safely", () => {
+ const objectWithoutStatus = { someOtherProperty: "value" };
const objectWithoutMessage = { anotherProperty: 42 };
expect(() => parseProviderError(objectWithoutStatus)).not.toThrow();
@@ -278,4 +278,4 @@ describe('providerErrorHandler', () => {
expect(result2.message).toBeUndefined();
});
});
-});
\ No newline at end of file
+});
diff --git a/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx b/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx
index 6f6a66df..0bedc7b2 100644
--- a/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx
+++ b/archon-ui-main/src/features/knowledge/views/KnowledgeView.tsx
@@ -4,9 +4,9 @@
*/
import { useEffect, useMemo, useRef, useState } from "react";
+import { useToast } from "@/features/shared/hooks/useToast";
import { CrawlingProgress } from "../../progress/components/CrawlingProgress";
import type { ActiveOperation } from "../../progress/types";
-import { useToast } from "@/features/shared/hooks/useToast";
import { AddKnowledgeDialog } from "../components/AddKnowledgeDialog";
import { KnowledgeHeader } from "../components/KnowledgeHeader";
import { KnowledgeList } from "../components/KnowledgeList";
diff --git a/archon-ui-main/src/features/mcp/components/McpConfigSection.tsx b/archon-ui-main/src/features/mcp/components/McpConfigSection.tsx
index c36b2f01..b5344bda 100644
--- a/archon-ui-main/src/features/mcp/components/McpConfigSection.tsx
+++ b/archon-ui-main/src/features/mcp/components/McpConfigSection.tsx
@@ -2,9 +2,9 @@ import { Copy, ExternalLink } from "lucide-react";
import type React from "react";
import { useState } from "react";
import { useToast } from "@/features/shared/hooks";
+import { copyToClipboard } from "../../shared/utils/clipboard";
import { Button, cn, glassmorphism, Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/primitives";
import type { McpServerConfig, McpServerStatus, SupportedIDE } from "../types";
-import { copyToClipboard } from "../../shared/utils/clipboard";
interface McpConfigSectionProps {
config?: McpServerConfig;
@@ -324,7 +324,8 @@ export const McpConfigSection: React.FC = ({ config, stat
Platform Note: The configuration below shows{" "}
{navigator.platform.toLowerCase().includes("win") ? "Windows" : "Linux/macOS"} format. Adjust paths
- according to your system. This setup is complex right now because Codex has some bugs with MCP currently.
+ according to your system. This setup is complex right now because Codex has some bugs with MCP
+ currently.
)}
diff --git a/archon-ui-main/src/features/mcp/hooks/useMcpQueries.ts b/archon-ui-main/src/features/mcp/hooks/useMcpQueries.ts
index aef5ec68..eaf8f404 100644
--- a/archon-ui-main/src/features/mcp/hooks/useMcpQueries.ts
+++ b/archon-ui-main/src/features/mcp/hooks/useMcpQueries.ts
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
-import { STALE_TIMES } from "../../shared/queryPatterns";
import { useSmartPolling } from "@/features/shared/hooks";
+import { STALE_TIMES } from "../../shared/config/queryPatterns";
import { mcpApi } from "../services";
// Query keys factory
diff --git a/archon-ui-main/src/features/mcp/services/mcpApi.ts b/archon-ui-main/src/features/mcp/services/mcpApi.ts
index 008c800c..d4b02ed4 100644
--- a/archon-ui-main/src/features/mcp/services/mcpApi.ts
+++ b/archon-ui-main/src/features/mcp/services/mcpApi.ts
@@ -1,4 +1,4 @@
-import { callAPIWithETag } from "../../shared/apiWithEtag";
+import { callAPIWithETag } from "../../shared/api/apiClient";
import type { McpClient, McpServerConfig, McpServerStatus, McpSessionInfo } from "../types";
export const mcpApi = {
diff --git a/archon-ui-main/src/features/progress/hooks/tests/useProgressQueries.test.ts b/archon-ui-main/src/features/progress/hooks/tests/useProgressQueries.test.ts
index 565919aa..d305a146 100644
--- a/archon-ui-main/src/features/progress/hooks/tests/useProgressQueries.test.ts
+++ b/archon-ui-main/src/features/progress/hooks/tests/useProgressQueries.test.ts
@@ -19,7 +19,7 @@ vi.mock("../../services", () => ({
}));
// Mock shared query patterns
-vi.mock("../../../shared/queryPatterns", () => ({
+vi.mock("../../../shared/config/queryPatterns", () => ({
DISABLED_QUERY_KEY: ["disabled"] as const,
STALE_TIMES: {
instant: 0,
diff --git a/archon-ui-main/src/features/progress/hooks/useProgressQueries.ts b/archon-ui-main/src/features/progress/hooks/useProgressQueries.ts
index ae82ba17..1ebec2a9 100644
--- a/archon-ui-main/src/features/progress/hooks/useProgressQueries.ts
+++ b/archon-ui-main/src/features/progress/hooks/useProgressQueries.ts
@@ -5,9 +5,9 @@
import { type UseQueryResult, useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react";
-import { APIServiceError } from "../../shared/errors";
-import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../shared/queryPatterns";
+import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../shared/config/queryPatterns";
import { useSmartPolling } from "../../shared/hooks";
+import { APIServiceError } from "../../shared/types/errors";
import { progressService } from "../services";
import type { ActiveOperationsResponse, ProgressResponse, ProgressStatus } from "../types";
diff --git a/archon-ui-main/src/features/progress/services/progressService.ts b/archon-ui-main/src/features/progress/services/progressService.ts
index d3f6e61e..ba0e68ba 100644
--- a/archon-ui-main/src/features/progress/services/progressService.ts
+++ b/archon-ui-main/src/features/progress/services/progressService.ts
@@ -3,7 +3,7 @@
* Uses ETag support for efficient polling
*/
-import { callAPIWithETag } from "../../shared/apiWithEtag";
+import { callAPIWithETag } from "../../shared/api/apiClient";
import type { ActiveOperationsResponse, ProgressResponse } from "../types";
export const progressService = {
diff --git a/archon-ui-main/src/features/projects/components/ProjectCard.tsx b/archon-ui-main/src/features/projects/components/ProjectCard.tsx
index df990710..a6b62349 100644
--- a/archon-ui-main/src/features/projects/components/ProjectCard.tsx
+++ b/archon-ui-main/src/features/projects/components/ProjectCard.tsx
@@ -1,7 +1,7 @@
import { motion } from "framer-motion";
import { Activity, CheckCircle2, ListTodo } from "lucide-react";
import type React from "react";
-import { isOptimistic } from "../../shared/optimistic";
+import { isOptimistic } from "@/features/shared/utils/optimistic";
import { OptimisticIndicator } from "../../ui/primitives/OptimisticIndicator";
import { cn } from "../../ui/primitives/styles";
import type { Project } from "../types";
diff --git a/archon-ui-main/src/features/projects/documents/components/DocumentCard.tsx b/archon-ui-main/src/features/projects/documents/components/DocumentCard.tsx
index 25b12365..06241a46 100644
--- a/archon-ui-main/src/features/projects/documents/components/DocumentCard.tsx
+++ b/archon-ui-main/src/features/projects/documents/components/DocumentCard.tsx
@@ -13,9 +13,9 @@ import {
} from "lucide-react";
import type React from "react";
import { memo, useCallback, useState } from "react";
+import { copyToClipboard } from "../../../shared/utils/clipboard";
import { Button } from "../../../ui/primitives";
import type { DocumentCardProps, DocumentType } from "../types";
-import { copyToClipboard } from "../../../shared/utils/clipboard";
const getDocumentIcon = (type?: DocumentType) => {
switch (type) {
diff --git a/archon-ui-main/src/features/projects/documents/hooks/useDocumentQueries.ts b/archon-ui-main/src/features/projects/documents/hooks/useDocumentQueries.ts
index 0a7d23ee..00c6eea6 100644
--- a/archon-ui-main/src/features/projects/documents/hooks/useDocumentQueries.ts
+++ b/archon-ui-main/src/features/projects/documents/hooks/useDocumentQueries.ts
@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
-import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../../shared/queryPatterns";
+import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../../shared/config/queryPatterns";
import { projectService } from "../../services";
import type { ProjectDocument } from "../types";
diff --git a/archon-ui-main/src/features/projects/hooks/useProjectQueries.ts b/archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
index ae216e66..946647ab 100644
--- a/archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
+++ b/archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
@@ -1,13 +1,13 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useSmartPolling } from "@/features/shared/hooks";
+import { useToast } from "@/features/shared/hooks/useToast";
import {
createOptimisticEntity,
type OptimisticEntity,
removeDuplicateEntities,
replaceOptimisticEntity,
-} from "@/features/shared/optimistic";
-import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../shared/queryPatterns";
-import { useSmartPolling } from "@/features/shared/hooks";
-import { useToast } from "@/features/shared/hooks/useToast";
+} from "@/features/shared/utils/optimistic";
+import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../shared/config/queryPatterns";
import { projectService } from "../services";
import type { CreateProjectRequest, Project, UpdateProjectRequest } from "../types";
@@ -36,9 +36,7 @@ export function useProjects() {
// Fetch project features
export function useProjectFeatures(projectId: string | undefined) {
- // TODO: Phase 4 - Add explicit typing: useQuery>>
- // See PRPs/local/frontend-state-management-refactor.md Phase 4: Configure Request Deduplication
- return useQuery({
+ return useQuery>>({
queryKey: projectId ? projectKeys.features(projectId) : DISABLED_QUERY_KEY,
queryFn: () => (projectId ? projectService.getProjectFeatures(projectId) : Promise.reject("No project ID")),
enabled: !!projectId,
@@ -208,6 +206,8 @@ export function useDeleteProject() {
// Don't refetch on success - trust optimistic update
// Only remove the specific project's detail data (including nested keys)
queryClient.removeQueries({ queryKey: projectKeys.detail(projectId), exact: false });
+ // Also remove the project's feature queries
+ queryClient.removeQueries({ queryKey: projectKeys.features(projectId), exact: false });
showToast("Project deleted successfully", "success");
},
});
diff --git a/archon-ui-main/src/features/projects/services/projectService.ts b/archon-ui-main/src/features/projects/services/projectService.ts
index f74675ca..58b1f3e6 100644
--- a/archon-ui-main/src/features/projects/services/projectService.ts
+++ b/archon-ui-main/src/features/projects/services/projectService.ts
@@ -3,8 +3,8 @@
* Focused service for project CRUD operations only
*/
-import { callAPIWithETag } from "../../shared/apiWithEtag";
-import { formatZodErrors, ValidationError } from "../../shared/errors";
+import { callAPIWithETag } from "../../shared/api/apiClient";
+import { formatZodErrors, ValidationError } from "../../shared/types/errors";
import { validateCreateProject, validateUpdateProject } from "../schemas";
import { formatRelativeTime } from "../shared/api";
import type { CreateProjectRequest, Project, ProjectFeatures, UpdateProjectRequest } from "../types";
diff --git a/archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx b/archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx
index 913964c6..c8e09464 100644
--- a/archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx
+++ b/archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx
@@ -2,7 +2,7 @@ import { Tag } from "lucide-react";
import type React from "react";
import { useCallback } from "react";
import { useDrag, useDrop } from "react-dnd";
-import { isOptimistic } from "../../../shared/optimistic";
+import { isOptimistic } from "@/features/shared/utils/optimistic";
import { OptimisticIndicator } from "../../../ui/primitives/OptimisticIndicator";
import { useTaskActions } from "../hooks";
import type { Assignee, Task, TaskPriority } from "../types";
diff --git a/archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts b/archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
index 55b4bbd0..2020a96d 100644
--- a/archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
+++ b/archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
@@ -1,11 +1,11 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
createOptimisticEntity,
- replaceOptimisticEntity,
- removeDuplicateEntities,
type OptimisticEntity,
-} from "@/features/shared/optimistic";
-import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../../shared/queryPatterns";
+ removeDuplicateEntities,
+ replaceOptimisticEntity,
+} from "@/features/shared/utils/optimistic";
+import { DISABLED_QUERY_KEY, STALE_TIMES } from "../../../shared/config/queryPatterns";
import { useSmartPolling } from "../../../shared/hooks";
import { useToast } from "../../../shared/hooks/useToast";
import { taskService } from "../services";
diff --git a/archon-ui-main/src/features/projects/tasks/services/taskService.ts b/archon-ui-main/src/features/projects/tasks/services/taskService.ts
index 223bdb73..dc2db1ed 100644
--- a/archon-ui-main/src/features/projects/tasks/services/taskService.ts
+++ b/archon-ui-main/src/features/projects/tasks/services/taskService.ts
@@ -3,8 +3,8 @@
* Focused service for task CRUD operations only
*/
-import { callAPIWithETag } from "../../../shared/apiWithEtag";
-import { formatZodErrors, ValidationError } from "../../../shared/errors";
+import { callAPIWithETag } from "../../../shared/api/apiClient";
+import { formatZodErrors, ValidationError } from "../../../shared/types/errors";
import { validateCreateTask, validateUpdateTask, validateUpdateTaskStatus } from "../schemas";
import type { CreateTaskRequest, DatabaseTaskStatus, Task, TaskCounts, UpdateTaskRequest } from "../types";
diff --git a/archon-ui-main/src/features/projects/tasks/services/tests/taskService.test.ts b/archon-ui-main/src/features/projects/tasks/services/tests/taskService.test.ts
index d86cc94d..d4215814 100644
--- a/archon-ui-main/src/features/projects/tasks/services/tests/taskService.test.ts
+++ b/archon-ui-main/src/features/projects/tasks/services/tests/taskService.test.ts
@@ -1,10 +1,10 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
-import { callAPIWithETag } from "../../../../shared/apiWithEtag";
+import { callAPIWithETag } from "../../../../shared/api/apiClient";
import type { CreateTaskRequest, DatabaseTaskStatus, Task, UpdateTaskRequest } from "../../types";
import { taskService } from "../taskService";
// Mock the API call
-vi.mock("../../../../shared/apiWithEtag", () => ({
+vi.mock("../../../../shared/api/apiClient", () => ({
callAPIWithETag: vi.fn(),
}));
diff --git a/archon-ui-main/src/features/settings/migrations/components/MigrationStatusCard.tsx b/archon-ui-main/src/features/settings/migrations/components/MigrationStatusCard.tsx
index 2b29531c..be4317a5 100644
--- a/archon-ui-main/src/features/settings/migrations/components/MigrationStatusCard.tsx
+++ b/archon-ui-main/src/features/settings/migrations/components/MigrationStatusCard.tsx
@@ -29,7 +29,8 @@ export function MigrationStatusCard() {