feat: Phase 3 - Fix optimistic updates with stable UUIDs and visual indicators#695
feat: Phase 3 - Fix optimistic updates with stable UUIDs and visual indicators#695
Conversation
…ndicators - Replace timestamp-based temp IDs with stable nanoid UUIDs - Create shared optimistic utilities module with type-safe functions - Add visual indicators (OptimisticIndicator component) for pending items - Update all mutation hooks (tasks, projects, knowledge) to use new utilities - Add optimistic state styling to TaskCard, ProjectCard, and KnowledgeCard - Add comprehensive unit tests for optimistic utilities - All tests passing, validation complete
WalkthroughAdds a type-safe optimistic-update utility module and tests, integrates nanoid-based stable optimistic IDs, introduces an OptimisticIndicator UI primitive and HOC, and wires optimistic entities, IDs, and UI styling into knowledge, projects, tasks, toasts, hooks, tests, and docs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Component (Form/Card)
participant Hook as use*Queries (Mutation)
participant Cache as Query Cache
participant API as Server
User->>UI: submit create action
UI->>Hook: mutate(payload)
Hook->>Hook: createOptimisticEntity / createOptimisticId
Hook->>Cache: insert optimistic entity (_localId, _optimistic=true)
Note right of UI #bfeef5: OptimisticIndicator rendered (Saving...)
Hook->>API: POST create
alt success
API-->>Hook: 200 OK (server entity)
Hook->>Cache: replaceOptimisticEntity(_localId → server entity)
Hook->>Cache: removeDuplicateEntities()
Note right of UI #e6fffb: OptimisticIndicator removed
else error
API-->>Hook: Error
Hook->>Cache: rollback to previous state
Note right of UI #fff0f0: OptimisticIndicator removed / error shown
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Remove outdated optimistic_updates.md - Create new concise documentation with file references - Document shared utilities API and patterns - Include performance characteristics and best practices - Reference actual implementation files instead of code examples - Add testing checklist and migration notes
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (5)
143-172: Optimistic KnowledgeItem lacks _optimistic metadata — indicator won’t show.Cards use
isOptimistic(item), but the optimistic item here doesn’t set_optimistic/_localId. Use the shared helper so UI indicators render and utilities can reconcile.Apply:
- // Generate temporary IDs - const tempProgressId = createOptimisticId(); - const tempItemId = createOptimisticId(); - - // Create optimistic knowledge item - const optimisticItem: KnowledgeItem = { - id: tempItemId, + // Generate temporary progress ID and optimistic entity + const tempProgressId = createOptimisticId(); + const optimisticItem = createOptimisticEntity<KnowledgeItem>( + { title: (() => { try { return new URL(request.url).hostname || "New crawl"; } catch { return "New crawl"; } })(), url: request.url, source_id: tempProgressId, source_type: "url", knowledge_type: request.knowledge_type || "technical", status: "processing", document_count: 0, code_examples_count: 0, metadata: { knowledge_type: request.knowledge_type || "technical", tags: request.tags || [], source_type: "url", status: "processing", description: `Crawling ${request.url}`, }, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), - }; + } as Omit<KnowledgeItem, "id"> + ); + const tempItemId = optimisticItem.id;And import the helper:
-import { createOptimisticId } from "@/features/shared/optimistic"; +import { createOptimisticId, createOptimisticEntity } from "@/features/shared/optimistic";
181-204: Filter-blind optimistic writes to summaries — items appear in wrong filtered views.Current
setQueriesDataupdates every summaries cache. Gate by each cache’s filter.Apply:
- queryClient.setQueriesData<KnowledgeItemsResponse>({ queryKey: knowledgeKeys.summariesPrefix() }, (old) => { - if (!old) { - return { - items: [optimisticItem], - total: 1, - page: 1, - per_page: 100, - pages: 1, - }; - } - return { - ...old, - items: [optimisticItem, ...old.items], - total: old.total + 1, - }; - }); + // Respect each cache's filter (knowledge_type, tags, etc.) + const entries = queryClient.getQueriesData<KnowledgeItemsResponse>({ + queryKey: knowledgeKeys.summariesPrefix(), + }); + for (const [qk, old] of entries) { + const filter = qk[qk.length - 1] as KnowledgeItemsFilter | undefined; + const matchesType = !filter?.knowledge_type || optimisticItem.knowledge_type === filter.knowledge_type; + const matchesTags = + !filter?.tags || filter.tags.every((t) => (optimisticItem.metadata?.tags ?? []).includes(t)); + if (!(matchesType && matchesTags)) continue; + if (!old) { + queryClient.setQueryData<KnowledgeItemsResponse>(qk, { + items: [optimisticItem], + total: 1, + page: 1, + per_page: 100, + pages: 1, + }); + } else { + queryClient.setQueryData<KnowledgeItemsResponse>(qk, { + ...old, + items: [optimisticItem, ...old.items], + total: (old.total ?? old.items.length) + 1, + }); + } + }
356-381: Same missing optimistic metadata for uploads.Use
createOptimisticEntityso the upload card shows the indicator.Apply:
- // Generate temporary IDs - const tempProgressId = createOptimisticId(); - const tempItemId = createOptimisticId(); + const tempProgressId = createOptimisticId(); - // Create optimistic knowledge item for the upload - const optimisticItem: KnowledgeItem = { - id: tempItemId, + // Create optimistic knowledge item for the upload + const optimisticItem = createOptimisticEntity<KnowledgeItem>({ title: file.name, url: `file://${file.name}`, source_id: tempProgressId, source_type: "file", knowledge_type: metadata.knowledge_type || "technical", status: "processing", document_count: 0, code_examples_count: 0, metadata: { knowledge_type: metadata.knowledge_type || "technical", tags: metadata.tags || [], source_type: "file", status: "processing", description: `Uploading ${file.name}`, file_name: file.name, }, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), - }; + } as Omit<KnowledgeItem, "id">); + const tempItemId = optimisticItem.id;And ensure the import includes
createOptimisticEntity.
388-404: Filter-blind optimistic writes for uploads — same issue as crawl.Mirror the filter-aware update logic used above to avoid polluted caches.
Can reuse the same looped
getQueriesData+ filter check shown for crawl.
555-563: Totals can desync when item wasn’t present in a filtered cache.You always decrement by 1. Compute removed count per cache.
Apply:
- queryClient.setQueryData<KnowledgeItemsResponse>(queryKey, { - ...data, - items: data.items.filter((item) => item.source_id !== sourceId), - total: Math.max(0, (data.total ?? data.items.length) - 1), - }); + const nextItems = data.items.filter((item) => item.source_id !== sourceId); + const removed = data.items.length - nextItems.length; + queryClient.setQueryData<KnowledgeItemsResponse>(queryKey, { + ...data, + items: nextItems, + total: Math.max(0, (data.total ?? data.items.length) - removed), + });
🧹 Nitpick comments (7)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (1)
295-299: Invalidate summaries on success for faster reconciliation.Consider also invalidating summaries to ensure quick convergence.
Apply:
queryClient.invalidateQueries({ queryKey: knowledgeKeys.lists() }); + queryClient.invalidateQueries({ queryKey: knowledgeKeys.summariesPrefix() }); queryClient.invalidateQueries({ queryKey: progressKeys.active() });archon-ui-main/src/features/shared/optimistic.test.ts (1)
1-110: Solid coverage for optimistic helpers.Consider adding a test for the “no-op” replace case (localId not found) and for
cleanOptimisticMetadatapreserving all non-meta fields.archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (1)
40-53: HOC API is static; consider prop-driven variant.Passing a static
isOptimisticat wrap time limits reuse. Optional: add a variant deriving the flag from props.For example:
+import type { ComponentType } from "react"; + +export function withOptimisticStylesBy<T extends { className?: string }>( + Component: ComponentType<T>, + isOptimisticFn: (props: T) => boolean, +) { + return (props: T) => ( + <Component + {...props} + className={cn( + props.className, + isOptimisticFn(props) && "opacity-70 animate-pulse ring-1 ring-cyan-400/20", + )} + /> + ); +}archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (2)
21-21: Polling interval too slow; use 1–2s active, 5–10s background.Guidelines call for faster smart polling. Set base to 2000ms and let
useSmartPollingstretch it in background.- const { refetchInterval } = useSmartPolling(20000); // 20 second base interval for projects + const { refetchInterval } = useSmartPolling(2000); // 2s active; hook will back off when unfocused
34-41: Reject with an Error, not a string.Use an Error object to preserve stack and align with error handling.
- queryFn: () => (projectId ? projectService.getProjectFeatures(projectId) : Promise.reject("No project ID")), + queryFn: () => + projectId ? projectService.getProjectFeatures(projectId) : Promise.reject(new Error("No project ID")),Also applies to: 38-39
archon-ui-main/src/features/shared/optimistic.ts (1)
48-59: Optional: provide an insert-on-miss variant to simplify call sites.Several callers need to add the server entity if
_localIdisn’t found. Consider a helper likereplaceOrInsertOptimisticEntity(…, position = "prepend")to centralize that logic.+export function replaceOrInsertOptimisticEntity<T extends { id: string }>( + entities: (T & Partial<OptimisticEntity>)[], + localId: string, + serverEntity: T, + position: "prepend" | "append" = "prepend", +): T[] { + const replaced = replaceOptimisticEntity(entities, localId, serverEntity); + const hasServer = replaced.some((e) => e.id === serverEntity.id); + const next = hasServer ? replaced : (position === "prepend" ? [serverEntity, ...replaced] : [...replaced, serverEntity]); + return removeDuplicateEntities(next); +}archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
92-101: Harden replace to handle missing _localId match; type the mutationIf replaceOptimisticEntity doesn't find the optimistic id, append the server task; add useMutation generics so serverTask is typed as Task (taskService.createTask returns Promise).
File: archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (same location as original comment)
- queryClient.setQueryData( - taskKeys.byProject(variables.project_id), - (tasks: (Task & Partial<OptimisticEntity>)[] = []) => { - const replaced = replaceOptimisticEntity(tasks, context?.optimisticId || "", serverTask); - return removeDuplicateEntities(replaced); - } - ); + queryClient.setQueryData( + taskKeys.byProject(variables.project_id), + (tasks: (Task & Partial<OptimisticEntity>)[] = []) => { + const localId = context?.optimisticId ?? ""; + const replaced = replaceOptimisticEntity(tasks, localId, serverTask); + const hasServer = replaced.some((t) => t.id === serverTask.id); + const next = hasServer ? replaced : [...replaced, serverTask]; + return removeDuplicateEntities(next); + }, + );If needed, add explicit generics:
- return useMutation({ + return useMutation<Task, Error, CreateTaskRequest, { previousTasks?: Task[]; optimisticId: string }>({
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
archon-ui-main/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (11)
archon-ui-main/package.json(1 hunks)archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx(4 hunks)archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts(3 hunks)archon-ui-main/src/features/projects/components/ProjectCard.tsx(5 hunks)archon-ui-main/src/features/projects/hooks/useProjectQueries.ts(4 hunks)archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx(4 hunks)archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts(3 hunks)archon-ui-main/src/features/shared/optimistic.test.ts(1 hunks)archon-ui-main/src/features/shared/optimistic.ts(1 hunks)archon-ui-main/src/features/ui/hooks/useToast.ts(2 hunks)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
archon-ui-main/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/**/*.{ts,tsx}: Use TanStack Query for all data fetching; avoid prop drilling
TypeScript: strict mode with no implicit any in frontend code
State naming: is[Action]ing for loading flags, [resource]Error for errors, selected[Resource] for current selection
Use HTTP polling with ETag caching; do not introduce WebSocket-based updates in the frontend
archon-ui-main/src/**/*.{ts,tsx}: WebSocket event failures (if any) should be logged and not crash the client; continue serving others
Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Use vertical slice architecture: place UI under src/features/[feature]/(components|hooks|services|types)
State naming: use is[Action]ing for loading, [resource]Error for errors, selected[Resource] for selections
Service method names: get[Resource]sByProject(projectId), getResource, create/update/delete patterns
Frontend TypeScript should be strict (no implicit any)
Files:
archon-ui-main/src/features/shared/optimistic.test.tsarchon-ui-main/src/features/projects/components/ProjectCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/ui/hooks/useToast.tsarchon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsxarchon-ui-main/src/features/projects/tasks/components/TaskCard.tsxarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/shared/optimistic.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/features/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/features/**/*.{ts,tsx}: Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Biome formatting in features: 120-character lines, double quotes, trailing commas
archon-ui-main/src/features/**/*.{ts,tsx}: Use Biome formatting/conventions in /src/features: 120-char lines, double quotes, trailing commas
Use useSmartPolling and polling intervals (1–2s active, 5–10s background) with smart pausing on tab inactivity
Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Do not use prop drilling for data fetching/state; rely on TanStack Query caches/selectors
Files:
archon-ui-main/src/features/shared/optimistic.test.tsarchon-ui-main/src/features/projects/components/ProjectCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/ui/hooks/useToast.tsarchon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsxarchon-ui-main/src/features/projects/tasks/components/TaskCard.tsxarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/shared/optimistic.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
**/*.{py,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Never return None/null to indicate failure; raise an exception with details instead
Files:
archon-ui-main/src/features/shared/optimistic.test.tsarchon-ui-main/src/features/projects/components/ProjectCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/ui/hooks/useToast.tsarchon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsxarchon-ui-main/src/features/projects/tasks/components/TaskCard.tsxarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/shared/optimistic.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Write frontend tests with Vitest and React Testing Library
Files:
archon-ui-main/src/features/shared/optimistic.test.ts
🧠 Learnings (4)
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/shared/optimistic.test.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsxarchon-ui-main/src/features/shared/optimistic.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-08-28T12:56:47.840Z
Learnt from: Wirasm
PR: coleam00/Archon#514
File: archon-ui-main/src/pages/ProjectPage.tsx:329-331
Timestamp: 2025-08-28T12:56:47.840Z
Learning: In the ProjectPage.tsx polling refactor, temporary project creation logic with progress cards was removed in favor of simpler modal loading states, as the complexity of managing in-flight temporary projects wasn't justified when HTTP polling would show new projects within seconds anyway.
Applied to files:
archon-ui-main/src/features/projects/components/ProjectCard.tsxarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
🧬 Code graph analysis (9)
archon-ui-main/src/features/shared/optimistic.test.ts (1)
archon-ui-main/src/features/shared/optimistic.ts (6)
createOptimisticId(23-25)createOptimisticEntity(30-42)isOptimistic(16-18)replaceOptimisticEntity(48-59)removeDuplicateEntities(65-74)cleanOptimisticMetadata(79-82)
archon-ui-main/src/features/projects/components/ProjectCard.tsx (2)
archon-ui-main/src/features/shared/optimistic.ts (1)
isOptimistic(16-18)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (1)
OptimisticIndicator(15-35)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (1)
archon-ui-main/src/features/shared/optimistic.ts (1)
createOptimisticId(23-25)
archon-ui-main/src/features/ui/hooks/useToast.ts (1)
archon-ui-main/src/features/shared/optimistic.ts (1)
createOptimisticId(23-25)
archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (2)
archon-ui-main/src/features/shared/optimistic.ts (1)
isOptimistic(16-18)archon-ui-main/src/features/ui/primitives/styles.ts (1)
cn(139-141)
archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx (2)
archon-ui-main/src/features/shared/optimistic.ts (1)
isOptimistic(16-18)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (1)
OptimisticIndicator(15-35)
archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx (2)
archon-ui-main/src/features/shared/optimistic.ts (1)
isOptimistic(16-18)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (1)
OptimisticIndicator(15-35)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (2)
archon-ui-main/src/features/shared/optimistic.ts (4)
createOptimisticEntity(30-42)OptimisticEntity(6-11)replaceOptimisticEntity(48-59)removeDuplicateEntities(65-74)archon-ui-main/src/features/projects/hooks/index.ts (1)
projectKeys(13-13)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (2)
archon-ui-main/src/features/shared/optimistic.ts (4)
createOptimisticEntity(30-42)OptimisticEntity(6-11)replaceOptimisticEntity(48-59)removeDuplicateEntities(65-74)archon-ui-main/src/features/projects/tasks/types/task.ts (1)
Task(47-71)
🔇 Additional comments (12)
archon-ui-main/src/features/ui/hooks/useToast.ts (1)
3-3: LGTM: stable IDs for toasts.Switching to
createOptimisticId()removes collision risk without behavior changes.Also applies to: 42-45
archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx (1)
49-51: Indicator depends on _optimistic flag — ensure producer sets it.This UI is correct, but without
_optimisticon created items the indicator and styles won’t trigger. See proposed fixes in useKnowledgeQueries.ts.Also applies to: 176-177, 244-245
archon-ui-main/src/features/projects/tasks/components/TaskCard.tsx (1)
39-41: LGTM: consistent optimistic visuals.Accents and indicator integration align with shared pattern; layout tweak with ml-auto is clean.
Also applies to: 160-161, 185-190
archon-ui-main/src/features/projects/components/ProjectCard.tsx (1)
32-34: LGTM: optimistic indicator and styling integrated cleanly.No API changes; consistent with Task/Knowledge cards.
Also applies to: 67-68, 80-94
archon-ui-main/package.json (1)
47-47: nanoid@5 — OK: CI & build images target Node 18 (ESM-compatible)..github/workflows/ci.yml sets NODE_VERSION='18' (actions/setup-node@v4); archon-ui-main/package.json declares "type": "module" and includes nanoid ^5.0.9; Dockerfiles use FROM node:18-alpine; local runner is v24.3.0 — Node ≥18 requirement satisfied.
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (2)
58-71: LGTM: optimistic project construction is correct and metadata-safe.
201-206: LGTM: targeted cache cleanup on delete.Removing detail queries (non-exact) is a good call; avoids stale nested caches without over-invalidation.
archon-ui-main/src/features/shared/optimistic.ts (1)
1-82: Well-factored optimistic helpers; APIs are cohesive and type-safe.The metadata contract, type guard, and utilities are consistent and minimal. Nice work.
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (4)
20-20: LGTM: smart polling interval matches guidelines for tasks.
59-73: LGTM: optimistic task construction is consistent with shared utils.
154-165: LGTM: selective counts invalidation and user feedback on status changes.
203-208: LGTM: post-delete invalidations reconcile ordering and counts.
| // Replace optimistic with server data | ||
| queryClient.setQueryData( | ||
| projectKeys.lists(), | ||
| (projects: (Project & Partial<OptimisticEntity>)[] = []) => { | ||
| const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject); | ||
| return removeDuplicateEntities(replaced); | ||
| } | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
Make replacement robust if the optimistic item isn’t found; also strongly type the mutation.
Fallback-insert the server entity when _localId isn’t matched to avoid losing the created project until refetch completes. Also, please type the mutation to the exact return type of projectService.createProject to prevent shape drift (e.g., Project vs { project: Project }).
- queryClient.setQueryData(
- projectKeys.lists(),
- (projects: (Project & Partial<OptimisticEntity>)[] = []) => {
- const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject);
- return removeDuplicateEntities(replaced);
- }
- );
+ queryClient.setQueryData(
+ projectKeys.lists(),
+ (projects: (Project & Partial<OptimisticEntity>)[] = []) => {
+ const localId = context?.optimisticId ?? "";
+ const replaced = replaceOptimisticEntity(projects, localId, newProject);
+ const hasServer = replaced.some((p) => p.id === newProject.id);
+ const next = hasServer ? replaced : [newProject, ...replaced];
+ return removeDuplicateEntities(next);
+ },
+ );If createProject returns { project: Project }, type the mutation accordingly; otherwise prefer Project:
- return useMutation({
+ return useMutation<Project, Error, CreateProjectRequest, { previousProjects?: Project[]; optimisticId: string }>({
mutationFn: (projectData: CreateProjectRequest) => projectService.createProject(projectData),Run to confirm the service return shape and catch any legacy temp-ID code:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect createProject signature/return usage
rg -n -C3 --type=ts 'createProject\s*\(' archon-ui-main/src/features/projects
# Ensure no leftover temp IDs or Date.now()-based IDs in UI code
rg -nP --type=ts --type=tsx '(temp-|Date\.now\s*\()' archon-ui-main/srcLength of output: 1453
Type mutation to service response & fallback-insert server project when optimistic replacement misses
projectService.createProject returns { project_id: string; project: Project; status: string } — type the mutation to that exact shape (or use Awaited<ReturnType>) and, in onSuccess, use the wrapped project (data.project) and fallback-insert it into the cached list if replaceOptimisticEntity didn't match the optimistic _localId (prevents losing a created project until refetch).
Files: archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (mutation / setQueryData) and archon-ui-main/src/features/projects/services/projectService.ts (createProject return shape)
Suggested changes:
- Make setQueryData robust (use server project, insert when optimistic not found):
- queryClient.setQueryData(
- projectKeys.lists(),
- (projects: (Project & Partial<OptimisticEntity>)[] = []) => {
- const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject);
- return removeDuplicateEntities(replaced);
- }
- );
+ // `data` is the mutation response; handle both raw Project or { project: Project } shapes
+ const serverProject = (data as any)?.project ?? (data as any);
+ queryClient.setQueryData(
+ projectKeys.lists(),
+ (projects: (Project & Partial<OptimisticEntity>)[] = []) => {
+ const localId = context?.optimisticId ?? "";
+ const replaced = replaceOptimisticEntity(projects, localId, serverProject);
+ const hasServer = replaced.some((p) => p.id === serverProject.id);
+ const next = hasServer ? replaced : [serverProject, ...replaced];
+ return removeDuplicateEntities(next);
+ },
+ );- Strongly type the mutation to the service return shape:
- return useMutation({
+ return useMutation<
+ Awaited<ReturnType<typeof projectService.createProject>>,
+ Error,
+ CreateProjectRequest,
+ { previousProjects?: Project[]; optimisticId: string }
+ >({
mutationFn: (projectData: CreateProjectRequest) => projectService.createProject(projectData),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Replace optimistic with server data | |
| queryClient.setQueryData( | |
| projectKeys.lists(), | |
| (projects: (Project & Partial<OptimisticEntity>)[] = []) => { | |
| const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject); | |
| return removeDuplicateEntities(replaced); | |
| } | |
| ); | |
| // `data` is the mutation response; handle both raw Project or { project: Project } shapes | |
| const serverProject = (data as any)?.project ?? (data as any); | |
| queryClient.setQueryData( | |
| projectKeys.lists(), | |
| (projects: (Project & Partial<OptimisticEntity>)[] = []) => { | |
| const localId = context?.optimisticId ?? ""; | |
| const replaced = replaceOptimisticEntity(projects, localId, serverProject); | |
| const hasServer = replaced.some((p) => p.id === serverProject.id); | |
| const next = hasServer ? replaced : [serverProject, ...replaced]; | |
| return removeDuplicateEntities(next); | |
| }, | |
| ); |
🤖 Prompt for AI Agents
In archon-ui-main/src/features/projects/hooks/useProjectQueries.ts around lines
97-104, the onSuccess handler is using newProject without typing the mutation to
the actual service response and can drop a created project if
replaceOptimisticEntity doesn't find the optimistic _localId; change the
mutation generic to the exact return shape (use Awaited<ReturnType<typeof
projectService.createProject>> or an explicit { project_id: string; project:
Project; status: string }), then in onSuccess read the server object from
data.project and when calling queryClient.setQueryData, run
replaceOptimisticEntity and if that replacement did not modify any entity (i.e.,
optimistic not found) push/insert the server project into the projects array
before deduping with removeDuplicateEntities so the created project is not lost.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (10)
PRPs/ai_docs/optimistic_updates.md (10)
9-10: Fix terminology: nanoid does not produce RFC 4122 UUIDsUse “unique IDs” (URL‑friendly IDs) instead of “UUIDs” to avoid misleading readers.
Apply this diff:
-- `createOptimisticId()` - Generates stable UUIDs using nanoid +- `createOptimisticId()` - Generates stable unique IDs using nanoid-- **nanoid**: v5.0.9 - UUID generation +- **nanoid**: v5.0.9 - Unique ID generation (URL‑friendly)Also applies to: 128-128
12-14: Qualify “race-condition safe” claim“Race-condition safe” is too absolute for docs. Reword to describe idempotency/out‑of‑order tolerance when matching by
_localId.-`replaceOptimisticEntity()` - Replaces optimistic items by `_localId` (race-condition safe) +`replaceOptimisticEntity()` - Replaces optimistic items by `_localId` (idempotent; tolerates retried or out‑of‑order responses)-2. **onSuccess**: Replace optimistic with server response - - Use `replaceOptimisticEntity()` matching by `_localId` +2. **onSuccess**: Replace optimistic with server response + - Use `replaceOptimisticEntity()` matching by `_localId` (idempotent)Also applies to: 33-35
18-21: Export the interface in the snippet for clarityReaders may copy/paste this; show
export interface.-interface OptimisticEntity { +export interface OptimisticEntity { _optimistic: boolean; _localId: string; }
27-28: Avoid brittle line-number references; point to symbols/files insteadLine numbers drift quickly. Reference module and function names or add permalinks.
Also applies to: 41-45
53-57: Document props and defaults for OptimisticIndicatorAdd a tiny props block (type and defaults) to make usage unambiguous.
Example to add below:
export interface OptimisticIndicatorProps { showSpinner?: boolean; // default: true pulseAnimation?: boolean; // default: true className?: string; }
93-99: Include a copy‑paste console script for race‑condition testingProvide a concrete example to help QA reproduce.
Suggested snippet to add:
// Create 10 tasks rapidly to test dedup/replace order Array.from({length: 10}).forEach((_, i) => { window.app.api.tasks.create({ title: `Race ${i}`, priority: 'medium' }); });
112-117: Avoid undocumented “~60% code reduction” claimEither link evidence or use qualitative language.
-- ~60% code reduction through shared utilities +- Significant code reduction through shared utilities
120-125: Add immutability guidance for TanStack Query cachesPrevent subtle cache issues by reminding not to mutate arrays/objects in place.
5. **Handle errors gracefully** - Always implement rollback in `onError` +6. **Preserve immutability** - Return new arrays/objects when updating cached data (avoid in‑place mutation) to ensure cache updates propagate
126-131: Version pinning nuanceDocs say “v5.0.9” while package.json likely uses ^5.0.9. Mirror the actual semver range to avoid confusion.
-- **nanoid**: v5.0.9 - Unique ID generation (URL‑friendly) +- **nanoid**: ^5.0.9 - Unique ID generation (URL‑friendly)
135-135: Fix markdownlint MD036: use a heading, not emphasized textConform to markdownlint and improve navigability.
-*Last updated: Phase 3 implementation (PR #695)* +### Last updated +Phase 3 implementation (PR #695)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
PRPs/ai_docs/optimistic_updates.md(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Applied to files:
PRPs/ai_docs/optimistic_updates.md
🪛 markdownlint-cli2 (0.17.2)
PRPs/ai_docs/optimistic_updates.md
135-135: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🔇 Additional comments (4)
PRPs/ai_docs/optimistic_updates.md (4)
1-8: Overall: strong, actionable guideStructure is clear, APIs and patterns are easy to follow. After the wording/verification tweaks above, this will be a solid reference.
Also applies to: 24-25, 50-58
60-74: Cannot verify — 'src/' not found in workspaceAutomated search failed (src/ missing); cannot confirm these assertions. Verify locally or re-run with repository available.
- Tasks: default priority "medium" — check src/features/projects/tasks/hooks/useTaskQueries.ts and src/features/projects/tasks/components/TaskCard.tsx for priority: "medium" or default in the create mutation.
- Projects: prd/data_schema set to null on optimistic create — check src/features/projects/hooks/useProjectQueries.ts and src/features/projects/components/ProjectCard.tsx for prd: null and data_schema: null.
- Knowledge: createOptimisticId used directly for progress tracking — check src/features/knowledge/hooks/useKnowledgeQueries.ts and src/features/knowledge/components/KnowledgeCard.tsx for createOptimisticId(...).
Suggested local commands:
rg -nP 'priority\s*:\s*"medium"' src
rg -nP '\b(prd|data_schema)\s*:\snull\b' src
rg -nP 'createOptimisticId\s(' src/features/knowledge
108-111: Add repo-wide verification for timestamp-based temp ID migrationPrevious run targeted
src(not present) and returnedsrc: No such file or directory (os error 2)and0matches. Run a repo-root search to ensure no timestamp-based temp IDs remain and confirm new API usage:#!/bin/bash # Repo-root search (exclude node_modules/dist/build) rg -n -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -P 'temp-\$\{?Date\.now\(\)\}?|temp-\s*\+\s*Date\.now\(\)|Date\.now\(\s*\)|new Date\(\)\.getTime\(\s*\)' || true # Confirm new API usage appears broadly rg -n -g '!**/node_modules/**' -g '!**/dist/**' -P '\bcreateOptimisticId\s*\(|\bcreateOptimisticEntity\b' | wc -l
75-78: Confirm toasts use createOptimisticId for IDsVerified — archon-ui-main/src/features/ui/hooks/useToast.ts uses createOptimisticId() to generate toast IDs (usage at line 43).
Address systematic review feedback on optimistic updates implementation: **Knowledge Queries (useKnowledgeQueries.ts):** - Add missing createOptimisticEntity import for type-safe optimistic creation - Implement filter-aware cache updates for crawl/upload flows to prevent items appearing in wrong filtered views - Fix total count calculation in deletion to accurately reflect removed items - Replace manual optimistic item creation with createOptimisticEntity<KnowledgeItem>() **Project Queries (useProjectQueries.ts):** - Add proper TypeScript mutation typing with Awaited<ReturnType<>> - Ensure type safety for createProject mutation response handling **OptimisticIndicator Component:** - Fix React.ComponentType import to use direct import instead of namespace - Add proper TypeScript ComponentType import for HOC function - Apply consistent Biome formatting **Documentation:** - Update performance characteristics with accurate bundlephobia metrics - Improve nanoid benchmark references and memory usage details All unit tests passing (90/90). Integration test failures expected without backend. Co-Authored-By: CodeRabbit Review <noreply@coderabbit.ai>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (1)
24-30: Polling faster in background than foreground (inverted)With baseInterval=20000, focused polling is 20s while blurred is 5s — backwards. Use a shorter active interval so background is not faster than foreground.
- const { refetchInterval } = useSmartPolling(20000); // 20 second base interval for projects + const { refetchInterval } = useSmartPolling(2000); // 2s active; hook already slows to 5s on blur
♻️ Duplicate comments (3)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (2)
54-59: Typed to service return — goodUsing Awaited<ReturnType> resolves prior typing drift concerns.
106-110: Fallback-insert server entity if optimistic replacement missesIf the optimistic _localId isn’t found, the created project can disappear until refetch finishes. Insert the server item when no match occurs.
- queryClient.setQueryData(projectKeys.lists(), (projects: (Project & Partial<OptimisticEntity>)[] = []) => { - const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject); - return removeDuplicateEntities(replaced); - }); + queryClient.setQueryData(projectKeys.lists(), (projects: (Project & Partial<OptimisticEntity>)[] = []) => { + const localId = context?.optimisticId ?? ""; + const replaced = replaceOptimisticEntity(projects, localId, newProject); + const hasServer = replaced.some((p) => p.id === newProject.id); + const next = hasServer ? replaced : [newProject, ...replaced]; + return removeDuplicateEntities(next); + });PRPs/ai_docs/optimistic_updates.md (1)
101-104: Performance section correctedClaims are now qualified and sourced. Looks good.
🧹 Nitpick comments (7)
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts (2)
89-105: Background polling continues while isActive=false — ensure consumers don’t gate on isActiveTest correctly expects refetchInterval=5000 on blur, but isActive=false. Downstream hooks should rely on refetchInterval (not isActive) for polling decisions to avoid unintentionally stopping background polls.
115-115: Redundant assertionThis repeats the prior expectation. Consider removing to keep the test focused.
archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx (2)
171-172: Optional UX: signal pending state via cursor/protectionConsider adding cursor-progress and temporarily disabling pointer events on the card when optimistic to reduce accidental actions.
- isHovered && "shadow-[0_0_30px_rgba(6,182,212,0.2)]", - "min-h-[240px] flex flex-col", - optimistic && "opacity-80 ring-1 ring-cyan-400/30", + isHovered && "shadow-[0_0_30px_rgba(6,182,212,0.2)]", + "min-h-[240px] flex flex-col", + optimistic && "opacity-80 ring-1 ring-cyan-400/30 cursor-progress",
239-239: A11y: expose saving state to ATConsider adding aria-live="polite" inside OptimisticIndicator (component-level) so “Saving…” is announced without extra wrappers.
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (1)
379-403: Repeat: remove ‘pages’ from cache literal or extend typeMirror the earlier fix here as well.
- queryClient.setQueryData<KnowledgeItemsResponse>(qk, { - ...old, - items: [optimisticItem, ...old.items], - total: (old.total ?? old.items.length) + 1, - }); + queryClient.setQueryData<KnowledgeItemsResponse>(qk, { + ...old, + items: [optimisticItem, ...old.items], + total: (old.total ?? old.items.length) + 1, + });PRPs/ai_docs/optimistic_updates.md (2)
9-11: nanoid doesn’t produce UUIDsChange “stable UUIDs” to “stable IDs” to avoid implying RFC‑4122 UUIDs.
-- `createOptimisticId()` - Generates stable UUIDs using nanoid +- `createOptimisticId()` - Generates stable IDs using nanoid
135-135: Fix markdownlint MD036 (no emphasis as heading)Use a real heading or plain text.
-*Last updated: Phase 3 implementation (PR #695)* +### Last updated +Phase 3 implementation (PR #695)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
PRPs/ai_docs/optimistic_updates.md(1 hunks)archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx(4 hunks)archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx(0 hunks)archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts(10 hunks)archon-ui-main/src/features/projects/hooks/useProjectQueries.ts(5 hunks)archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts(4 hunks)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- archon-ui-main/src/features/knowledge/components/KnowledgeCardTags.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx
🧰 Additional context used
📓 Path-based instructions (4)
archon-ui-main/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/**/*.{ts,tsx}: Use TanStack Query for all data fetching; avoid prop drilling
TypeScript: strict mode with no implicit any in frontend code
State naming: is[Action]ing for loading flags, [resource]Error for errors, selected[Resource] for current selection
Use HTTP polling with ETag caching; do not introduce WebSocket-based updates in the frontend
archon-ui-main/src/**/*.{ts,tsx}: WebSocket event failures (if any) should be logged and not crash the client; continue serving others
Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Use vertical slice architecture: place UI under src/features/[feature]/(components|hooks|services|types)
State naming: use is[Action]ing for loading, [resource]Error for errors, selected[Resource] for selections
Service method names: get[Resource]sByProject(projectId), getResource, create/update/delete patterns
Frontend TypeScript should be strict (no implicit any)
Files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.tsarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
archon-ui-main/src/features/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/features/**/*.{ts,tsx}: Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Biome formatting in features: 120-character lines, double quotes, trailing commas
archon-ui-main/src/features/**/*.{ts,tsx}: Use Biome formatting/conventions in /src/features: 120-char lines, double quotes, trailing commas
Use useSmartPolling and polling intervals (1–2s active, 5–10s background) with smart pausing on tab inactivity
Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Do not use prop drilling for data fetching/state; rely on TanStack Query caches/selectors
Files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.tsarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
**/*.{py,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Never return None/null to indicate failure; raise an exception with details instead
Files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.tsarchon-ui-main/src/features/knowledge/components/KnowledgeCard.tsxarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
archon-ui-main/src/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Write frontend tests with Vitest and React Testing Library
Files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
🧠 Learnings (9)
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Use useSmartPolling and polling intervals (1–2s active, 5–10s background) with smart pausing on tab inactivity
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.tsPRPs/ai_docs/optimistic_updates.mdarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Use HTTP polling with ETag caching; do not introduce WebSocket-based updates in the frontend
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
📚 Learning: 2025-08-28T13:51:59.203Z
Learnt from: Wirasm
PR: coleam00/Archon#514
File: archon-ui-main/src/services/pollingService.ts:60-102
Timestamp: 2025-08-28T13:51:59.203Z
Learning: The pollingService.ts file was removed from the codebase after initially being added in PR #514, confirming the team's decision to consolidate on the usePolling hook pattern rather than maintaining separate polling services.
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
📚 Learning: 2025-08-28T13:50:10.499Z
Learnt from: Wirasm
PR: coleam00/Archon#514
File: archon-ui-main/src/services/pollingService.ts:38-45
Timestamp: 2025-08-28T13:50:10.499Z
Learning: The pollingService.ts file is being deprecated in favor of the usePolling hook from usePolling.ts. The team has decided to consolidate on the usePolling hook pattern rather than maintaining separate polling services, as it provides better React integration and reduces code duplication.
Applied to files:
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.tsarchon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-08-28T12:56:47.840Z
Learnt from: Wirasm
PR: coleam00/Archon#514
File: archon-ui-main/src/pages/ProjectPage.tsx:329-331
Timestamp: 2025-08-28T12:56:47.840Z
Learning: In the ProjectPage.tsx polling refactor, temporary project creation logic with progress cards was removed in favor of simpler modal loading states, as the complexity of managing in-flight temporary projects wasn't justified when HTTP polling would show new projects within seconds anyway.
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Use TanStack Query for all data fetching; avoid prop drilling
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
🧬 Code graph analysis (3)
archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx (2)
archon-ui-main/src/features/shared/optimistic.ts (1)
isOptimistic(16-18)archon-ui-main/src/features/ui/primitives/OptimisticIndicator.tsx (1)
OptimisticIndicator(16-30)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (4)
archon-ui-main/src/features/shared/optimistic.ts (2)
createOptimisticId(23-25)createOptimisticEntity(30-42)archon-ui-main/src/features/knowledge/types/knowledge.ts (3)
KnowledgeItem(26-39)KnowledgeItemsResponse(98-103)KnowledgeItemsFilter(127-134)archon-ui-main/src/features/ui/hooks/useSmartPolling.ts (1)
useSmartPolling(8-66)archon-ui-main/src/features/shared/queryPatterns.ts (1)
STALE_TIMES(17-24)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (1)
archon-ui-main/src/features/shared/optimistic.ts (4)
createOptimisticEntity(30-42)OptimisticEntity(6-11)replaceOptimisticEntity(48-59)removeDuplicateEntities(65-74)
🪛 markdownlint-cli2 (0.17.2)
PRPs/ai_docs/optimistic_updates.md
135-135: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🔇 Additional comments (14)
archon-ui-main/src/features/ui/hooks/tests/useSmartPolling.test.ts (1)
134-141: LGTM — consistent 5s background interval across base intervalsThe expectations reflect the new 5s background behavior.
archon-ui-main/src/features/knowledge/components/KnowledgeCard.tsx (3)
9-9: Icon import cleanupImports match actual usage. Good.
13-16: Optimistic utilities and indicator wired in correctlyConsistent with shared optimistic pattern.
49-51: Optimistic flag computation is correctNo extra work; keep as-is.
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (1)
68-81: Confirm server expectations for nullable fieldsprd and data are set to undefined. If the API expects explicit nulls, initialize with null to avoid patch/merge surprises.
- prd: undefined, - data: undefined, + prd: null, + data: null,archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (8)
8-8: Shared optimistic utilities import — goodAligns with Phase 3 utilities.
32-38: Key includes domain/limit/offset — good cache scopingPrevents collisions across filters.
66-66: Opts plumbed through — OKSignature addition is consistent with key factory.
139-167: Optimistic crawl item creation looks soundStable local ID, processing status, and mirrored metadata are correct.
353-353: Temporary progress ID via createOptimisticId — goodConsistent with crawl flow.
356-377: Optimistic upload item creation looks soundSame note about avoiding excess 'pages' when writing caches applies below.
556-564: Totals adjusted by actual removed count — niceThis fixes under/over-count issues when multiple removals occur.
776-778: Smart polling integration — OKRefetch interval toggles with active operations; hook handles focus/visibility.
PRPs/ai_docs/optimistic_updates.md (1)
66-69: Doc/code mismatch: data vs data_schemaProjects code initializes data (not data_schema). Align the doc with implementation or vice versa.
-- Handles `prd: null`, `data_schema: null` for new projects +- Handles `prd: null`, `data: null` for new projects
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (1)
106-110: Fallback-insert server project if optimistic replacement misses.If the optimistic _localId isn’t found (e.g., cache refresh/race), the created project can disappear until refetch completes. Insert the server entity when no match is replaced, then dedupe.
- // Replace optimistic with server data - queryClient.setQueryData(projectKeys.lists(), (projects: (Project & Partial<OptimisticEntity>)[] = []) => { - const replaced = replaceOptimisticEntity(projects, context?.optimisticId || "", newProject); - return removeDuplicateEntities(replaced); - }); + // Replace optimistic with server data; insert if optimistic was not found + queryClient.setQueryData(projectKeys.lists(), (projects: (Project & Partial<OptimisticEntity>)[] = []) => { + const localId = context?.optimisticId ?? ""; + const replaced = replaceOptimisticEntity(projects, localId, newProject); + const hasServer = replaced.some((p) => p.id === newProject.id); + const next = hasServer ? replaced : [newProject, ...replaced]; + return removeDuplicateEntities(next); + });archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (1)
377-401: Drop the excesspagesproperty from upload summaries cache updateSame issue as with the crawl flow - the
KnowledgeItemsResponsetype doesn't include apagesproperty, which will cause TypeScript excess property checking errors.Apply this diff to remove the excess
pagesproperty:- queryClient.setQueryData<KnowledgeItemsResponse>(qk, { - items: [optimisticItem], - total: 1, - page: 1, - per_page: 100, - pages: 1, - }); + queryClient.setQueryData<KnowledgeItemsResponse>(qk, { + items: [optimisticItem], + total: 1, + page: 1, + per_page: 100, + });
🧹 Nitpick comments (1)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (1)
68-81: Tiny tidy-ups for optimistic entity literal.
- Compute now once to avoid double Date(); drop explicit undefineds; optionally assert shape with satisfies for compile-time safety.
- // Create optimistic project with stable ID - const optimisticProject = createOptimisticEntity<Project>({ + // Create optimistic project with stable ID + const nowIso = new Date().toISOString(); + const optimisticProject = createOptimisticEntity<Project>( + { title: newProjectData.title, description: newProjectData.description, github_repo: newProjectData.github_repo, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), + created_at: nowIso, + updated_at: nowIso, docs: [], features: [], - prd: undefined, - data: undefined, pinned: false, - }); + } satisfies Omit<Project, "id" | keyof OptimisticEntity>, + );Also applies to: 89-90
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts(10 hunks)archon-ui-main/src/features/projects/hooks/useProjectQueries.ts(6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
archon-ui-main/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/**/*.{ts,tsx}: Use TanStack Query for all data fetching; avoid prop drilling
TypeScript: strict mode with no implicit any in frontend code
State naming: is[Action]ing for loading flags, [resource]Error for errors, selected[Resource] for current selection
Use HTTP polling with ETag caching; do not introduce WebSocket-based updates in the frontend
archon-ui-main/src/**/*.{ts,tsx}: WebSocket event failures (if any) should be logged and not crash the client; continue serving others
Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Use vertical slice architecture: place UI under src/features/[feature]/(components|hooks|services|types)
State naming: use is[Action]ing for loading, [resource]Error for errors, selected[Resource] for selections
Service method names: get[Resource]sByProject(projectId), getResource, create/update/delete patterns
Frontend TypeScript should be strict (no implicit any)
Files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts
archon-ui-main/src/features/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
archon-ui-main/src/features/**/*.{ts,tsx}: Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Biome formatting in features: 120-character lines, double quotes, trailing commas
archon-ui-main/src/features/**/*.{ts,tsx}: Use Biome formatting/conventions in /src/features: 120-char lines, double quotes, trailing commas
Use useSmartPolling and polling intervals (1–2s active, 5–10s background) with smart pausing on tab inactivity
Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Do not use prop drilling for data fetching/state; rely on TanStack Query caches/selectors
Files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts
**/*.{py,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Never return None/null to indicate failure; raise an exception with details instead
Files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts
🧠 Learnings (7)
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Follow TanStack Query patterns: query-keys factory, smart polling via useSmartPolling, optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts
📚 Learning: 2025-08-28T12:56:47.840Z
Learnt from: Wirasm
PR: coleam00/Archon#514
File: archon-ui-main/src/pages/ProjectPage.tsx:329-331
Timestamp: 2025-08-28T12:56:47.840Z
Learning: In the ProjectPage.tsx polling refactor, temporary project creation logic with progress cards was removed in favor of simpler modal loading states, as the complexity of managing in-flight temporary projects wasn't justified when HTTP polling would show new projects within seconds anyway.
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Expose progress via dedicated hooks (e.g., useCrawlProgressPolling, useProjectTasks) instead of ad-hoc timers
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Frontend data fetching must use TanStack Query (no prop drilling) with query key factories, smart polling, and optimistic updates with rollback
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.tsarchon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Service method names: get[Resource]sByProject(projectId), get[Resource](id), create/update/delete patterns
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-12T13:47:04.545Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-12T13:47:04.545Z
Learning: Applies to archon-ui-main/src/**/*.{ts,tsx} : Use TanStack Query for all data fetching; avoid prop drilling
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
📚 Learning: 2025-09-13T15:53:40.757Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-13T15:53:40.757Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Use useSmartPolling and polling intervals (1–2s active, 5–10s background) with smart pausing on tab inactivity
Applied to files:
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts
🧬 Code graph analysis (2)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (2)
archon-ui-main/src/features/ui/hooks/useSmartPolling.ts (1)
useSmartPolling(8-66)archon-ui-main/src/features/shared/optimistic.ts (4)
createOptimisticEntity(30-42)OptimisticEntity(6-11)replaceOptimisticEntity(48-59)removeDuplicateEntities(65-74)
archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (4)
archon-ui-main/src/features/shared/optimistic.ts (2)
createOptimisticId(23-25)createOptimisticEntity(30-42)archon-ui-main/src/features/knowledge/types/knowledge.ts (3)
KnowledgeItem(26-39)KnowledgeItemsResponse(98-103)KnowledgeItemsFilter(127-134)archon-ui-main/src/features/ui/hooks/useSmartPolling.ts (1)
useSmartPolling(8-66)archon-ui-main/src/features/shared/queryPatterns.ts (1)
STALE_TIMES(17-24)
🔇 Additional comments (8)
archon-ui-main/src/features/projects/hooks/useProjectQueries.ts (3)
2-7: Good adoption of shared optimistic utilities.Imports are cohesive and reflect the new optimistic helpers and type usage.
54-59: Strong mutation typing: nice.Typing to Awaited<ReturnType> and explicit context type removes shape drift.
26-26: Approve: 2s smart polling validated (ETag/conditional requests in place).
listProjects calls callAPIWithETag("/api/projects") and apiWithEtag.ts defers ETag/If-None-Match handling to the browser (transparent 304s) — 2s polling + refetchOnWindowFocus is acceptable.archon-ui-main/src/features/knowledge/hooks/useKnowledgeQueries.ts (5)
352-375: Consistent optimistic entity creation for uploadsThe implementation correctly uses
createOptimisticId()andcreateOptimisticEntity(), maintaining consistency with the crawl flow. The optimistic entity includes all required KnowledgeItem fields with appropriate defaults.
556-562: Improved delete logic with accurate count calculationThe refactored delete logic properly calculates the number of removed items and adjusts the total count accurately, replacing the previous fixed subtraction approach. This is more robust and handles edge cases correctly.
775-775: Smart polling integration follows TanStack Query best practicesThe single-line
useSmartPollingcall with conditional intervals based on active operations aligns with the coding guidelines for smart polling patterns and proper resource management.
829-846: Enhanced pagination support with enabled optionThe addition of the
enabledoption touseKnowledgeChunksprovides better control over query execution, following TanStack Query patterns. The implementation correctly defaults totruewhen not specified and properly incorporates the option into the query configuration.
852-869: Consistent pagination enhancement for code examplesSimilar to chunks, the
useKnowledgeCodeExampleshook now supports theenabledoption, maintaining consistency across the pagination-enabled hooks and following TanStack Query best practices.
…ndicators (coleam00#695) * feat: Phase 3 - Fix optimistic updates with stable UUIDs and visual indicators - Replace timestamp-based temp IDs with stable nanoid UUIDs - Create shared optimistic utilities module with type-safe functions - Add visual indicators (OptimisticIndicator component) for pending items - Update all mutation hooks (tasks, projects, knowledge) to use new utilities - Add optimistic state styling to TaskCard, ProjectCard, and KnowledgeCard - Add comprehensive unit tests for optimistic utilities - All tests passing, validation complete * docs: Update optimistic updates documentation with Phase 3 patterns - Remove outdated optimistic_updates.md - Create new concise documentation with file references - Document shared utilities API and patterns - Include performance characteristics and best practices - Reference actual implementation files instead of code examples - Add testing checklist and migration notes * fix: resolve CodeRabbit review issues for Phase 3 optimistic updates Address systematic review feedback on optimistic updates implementation: **Knowledge Queries (useKnowledgeQueries.ts):** - Add missing createOptimisticEntity import for type-safe optimistic creation - Implement filter-aware cache updates for crawl/upload flows to prevent items appearing in wrong filtered views - Fix total count calculation in deletion to accurately reflect removed items - Replace manual optimistic item creation with createOptimisticEntity<KnowledgeItem>() **Project Queries (useProjectQueries.ts):** - Add proper TypeScript mutation typing with Awaited<ReturnType<>> - Ensure type safety for createProject mutation response handling **OptimisticIndicator Component:** - Fix React.ComponentType import to use direct import instead of namespace - Add proper TypeScript ComponentType import for HOC function - Apply consistent Biome formatting **Documentation:** - Update performance characteristics with accurate bundlephobia metrics - Improve nanoid benchmark references and memory usage details All unit tests passing (90/90). Integration test failures expected without backend. Co-Authored-By: CodeRabbit Review <noreply@coderabbit.ai> * Adjust polling interval and clean knowledge cache --------- Co-authored-by: CodeRabbit Review <noreply@coderabbit.ai>
Pull Request
Summary
Implements Phase 3 of the frontend state management refactor by replacing timestamp-based temporary IDs with stable UUIDs, creating shared optimistic utilities, and adding visual indicators for pending items. This ensures no duplicate items during rapid creation and provides clear visual feedback during async operations.
Changes Made
nanoidpackage for stable UUID generation (v5.0.9)src/features/shared/optimistic.ts) with type-safe functionsDate.now()timestamp-based ID generation withcreateOptimisticId()using nanoidOptimisticIndicatorcomponent with spinner and "Saving..." textType of Change
Affected Services
Testing
Test Evidence
Checklist
Breaking Changes
This PR introduces a clean break from the old temporary ID system:
temp-${Date.now()}) are replacedAdditional Notes
Key Improvements:
OptimisticEntityinterfaceremoveDuplicateEntitiesprevents any duplicate itemsShared Utilities API:
createOptimisticId()- Generate stable UUIDcreateOptimisticEntity<T>()- Create optimistic entity with metadataisOptimistic()- Type guard to check optimistic statereplaceOptimisticEntity()- Replace by localId (handles race conditions)removeDuplicateEntities()- Deduplicate after replacementcleanOptimisticMetadata()- Remove optimistic fieldsPerformance Impact:
Migration from PRP:
Based on
PRPs/completed/story_phase_3_fix_optimistic_updates.md🚀 Ready for review!
Summary by CodeRabbit
New Features
Refactor
Behavior
Tests
Chores