Fix task description sync issue with smart merge strategy#625
Fix task description sync issue with smart merge strategy#625
Conversation
Resolves sync issue where task updates (description, priority) would disappear after page refresh due to optimistic updates being overwritten by potentially stale server responses. ## Problem - Task description/priority changes show immediately (optimistic update) - After page refresh, changes disappear - Root cause: onSuccess handler unconditionally replaces optimistic data with server response, even when server data is stale ## Solution Implement smart merge strategy in useUpdateTask mutation: - Compare timestamps between optimistic and server data - If server data is newer/equal: use server data completely - If server data appears stale: merge carefully to preserve user updates - Graceful fallback to server data on timestamp parsing errors ## Impact - ✅ Fixes task description sync issue - ✅ Fixes frontend-only priority update persistence - ✅ Maintains existing behavior for normal cases - ✅ All existing tests pass (7/7) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughUpdate to the task update mutation: optimistic updates now include an ISO Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant UI as UI
participant Hook as useUpdateTask (mutation)
participant Cache as Client Cache
participant API as Server API
Note over UI,Hook: User triggers task update (optimistic)
UI->>Hook: mutate({ taskId, updates })
Hook->>Cache: Apply optimistic update (set updated_at = nowIso)
Hook->>API: PATCH /tasks/:taskId (updates)
API-->>Hook: 200 OK (serverTask)
Note over Hook: onSuccess invoked with { updates, taskId }
alt server.updated_at >= optimistic.updated_at
Hook->>Cache: Replace task with serverTask
else server appears older than optimistic
Hook->>Cache: Merge serverTask + optimistic updates\n(preserve optimistic updated_at)
end
opt Status changed in updates
Hook->>Cache: Invalidate project task counts
end
Hook-->>UI: Show success toast
Note over Hook: If timestamp parse fails → warn & use serverTask
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 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 |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
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 (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
112-116: Bump optimistic updated_at in onMutate to make smart-merge effective.Without updating
updated_atduring the optimistic write, the server will almost always win (equal/newer), undermining the merge strategy and re-droppingpriority.- queryClient.setQueryData<Task[]>(taskKeys.all(projectId), (old) => { - if (!old) return old; - return old.map((task) => (task.id === taskId ? { ...task, ...updates } : task)); - }); + queryClient.setQueryData<Task[]>(taskKeys.all(projectId), (old) => { + if (!old) return old; + const nowIso = new Date().toISOString(); + return old.map((task) => + task.id === taskId ? { ...task, ...updates, updated_at: nowIso } : task, + ); + });
🧹 Nitpick comments (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
145-147: Optional: prefer “>” over “>=” to avoid tie-bias toward server.If server timestamps are second-precision and client is ms-precision, equality may happen; using “>” reduces unintended server preference. This is lower priority if you adopt the “carryClientOnly” approach above.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Implement optimistic updates with rollback in TanStack Query mutations
📚 Learning: 2025-09-06T20:04:08.111Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Implement optimistic updates with rollback in TanStack Query mutations
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-09-06T20:05:27.061Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-06T20:05:27.061Z
Learning: Applies to archon-ui-main/src/services/**/*.ts : Use GET /api/projects/{id}/tasks (not getTasks) for project tasks
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-09-06T20:04:08.111Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/tasks/**/*.{ts,tsx} : Use task status values directly from the database: todo, doing, review, done (no UI mapping)
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
| onSuccess: (data, { updates, taskId }) => { | ||
| // Smart merge: preserve optimistic updates if server data appears stale | ||
| queryClient.setQueryData<Task[]>(taskKeys.all(projectId), (old) => { | ||
| if (!old) return old; | ||
|
|
||
| return old.map((task) => { | ||
| if (task.id !== taskId) return task; | ||
|
|
||
| try { | ||
| // Compare timestamps to detect stale server responses | ||
| const serverUpdatedAt = new Date(data.updated_at); | ||
| const optimisticUpdatedAt = new Date(task.updated_at); | ||
|
|
||
| // If server data is newer or equal, use it completely | ||
| if (serverUpdatedAt >= optimisticUpdatedAt) { | ||
| return data; | ||
| } | ||
|
|
||
| // Server data appears stale - merge carefully | ||
| // Keep server data for computed fields but preserve optimistic updates | ||
| const mergedTask = { | ||
| ...data, // Start with server data for computed fields | ||
| ...updates, // Apply the original updates that were made | ||
| updated_at: task.updated_at, // Keep the more recent optimistic timestamp | ||
| }; | ||
|
|
||
| return mergedTask; | ||
| } catch (timestampError) { | ||
| // If timestamp parsing fails, fall back to server data | ||
| // This maintains existing behavior for edge cases | ||
| console.warn('Failed to parse timestamps for smart merge, using server data:', timestampError); | ||
| return data; | ||
| } | ||
| }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Add tests for stale/newer/invalid timestamp paths and priority preservation.
Cover: server newer, server stale, invalid timestamp (NaN), updates without priority while cached task has priority.
Run to locate Task/priority definitions and other updated_at usages:
🏁 Script executed:
#!/bin/bash
# Inspect Task type and presence of `priority`
fd -t f -a src | rg -nP '(^|\b)(interface|type)\s+Task\b' -n -C3
rg -n --type=ts '\bpriority\b' -C2
# Check other optimistic updates bumping updated_at
rg -n --type=ts 'onMutate:.*update' -C3
rg -n --type=ts '\bupdated_at\b' -C2Length of output: 43210
Add tests for timestamp-based merge behaviors and priority retention
Cover these scenarios in useTaskQueries.test.ts:
- server response newer than optimistic update → replace task with server data
- server response older than optimistic update → smart-merge and preserve updates
- invalid
updated_at(NaN) → fallback to server data - cached task has a
prioritywhileupdates(and server data) lack it → ensureprioritysurvives the merge
🤖 Prompt for AI Agents
In archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts around
lines 132 to 167, add unit tests in useTaskQueries.test.ts that cover
timestamp-based merge behavior: 1) simulate an optimistic update then a server
response with updated_at newer than the optimistic task and assert the cached
task is replaced by server data; 2) simulate a server response older than the
optimistic task and assert the cache performs the smart-merge (server computed
fields from response but preserves optimistic updates and keeps the optimistic
updated_at); 3) simulate a server response with invalid updated_at (NaN or
unparsable) and assert the fallback returns server data; 4) create a cached task
that has a priority field while both updates and server response lack priority
and assert the merged task retains the cached priority; set up the queryClient
initial cache, call the onSuccess handler with appropriate data/updates/taskId
for each scenario, and assert the final queryClient.getQueryData for
taskKeys.all(projectId) matches expected merged/replaced result.
| try { | ||
| // Compare timestamps to detect stale server responses | ||
| const serverUpdatedAt = new Date(data.updated_at); | ||
| const optimisticUpdatedAt = new Date(task.updated_at); | ||
|
|
||
| // If server data is newer or equal, use it completely | ||
| if (serverUpdatedAt >= optimisticUpdatedAt) { | ||
| return data; | ||
| } | ||
|
|
||
| // Server data appears stale - merge carefully | ||
| // Keep server data for computed fields but preserve optimistic updates | ||
| const mergedTask = { | ||
| ...data, // Start with server data for computed fields | ||
| ...updates, // Apply the original updates that were made | ||
| updated_at: task.updated_at, // Keep the more recent optimistic timestamp | ||
| }; | ||
|
|
||
| return mergedTask; | ||
| } catch (timestampError) { | ||
| // If timestamp parsing fails, fall back to server data | ||
| // This maintains existing behavior for edge cases | ||
| console.warn('Failed to parse timestamps for smart merge, using server data:', timestampError); | ||
| return data; | ||
| } | ||
| }); | ||
| }); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fix timestamp handling: Invalid Date doesn’t throw; also retain client-only fields (e.g., priority) regardless of freshness.
new Date(...) never throws; invalid inputs yield “Invalid Date” (NaN time), so the try/catch won’t run and the comparison will mis-route to the stale-merge path. Additionally, when the server is fresher, client-only fields like priority are still dropped. Replace the block to (1) compare via Date.parse with explicit validity checks and (2) always preserve client-only fields when present.
Apply:
- try {
- // Compare timestamps to detect stale server responses
- const serverUpdatedAt = new Date(data.updated_at);
- const optimisticUpdatedAt = new Date(task.updated_at);
-
- // If server data is newer or equal, use it completely
- if (serverUpdatedAt >= optimisticUpdatedAt) {
- return data;
- }
-
- // Server data appears stale - merge carefully
- // Keep server data for computed fields but preserve optimistic updates
- const mergedTask = {
- ...data, // Start with server data for computed fields
- ...updates, // Apply the original updates that were made
- updated_at: task.updated_at, // Keep the more recent optimistic timestamp
- };
-
- return mergedTask;
- } catch (timestampError) {
- // If timestamp parsing fails, fall back to server data
- // This maintains existing behavior for edge cases
- console.warn('Failed to parse timestamps for smart merge, using server data:', timestampError);
- return data;
- }
+ // Compare timestamps robustly; Date.parse returns NaN for invalid values (no exception)
+ const serverMs = Date.parse(data.updated_at);
+ const optimisticMs = Date.parse(task.updated_at);
+
+ // Carry over client-only fields (e.g., priority) when not part of this update
+ const carryClientOnly = (t: Task) =>
+ (t as any).priority !== undefined && (updates as any).priority === undefined
+ ? { priority: (t as any).priority }
+ : {};
+
+ // Fallback: if either timestamp is invalid, prefer server while keeping client-only fields
+ if (!Number.isFinite(serverMs) || !Number.isFinite(optimisticMs)) {
+ console.warn("Invalid updated_at; preferring server while keeping client-only fields");
+ return { ...data, ...carryClientOnly(task) } as Task;
+ }
+
+ if (serverMs >= optimisticMs) {
+ // Server is fresher or equal: prefer server but keep client-only fields
+ return { ...data, ...carryClientOnly(task) } as Task;
+ }
+
+ // Server appears stale: start from server, reapply updates, keep optimistic timestamp, and client-only fields
+ return {
+ ...data,
+ ...carryClientOnly(task),
+ ...updates,
+ updated_at: task.updated_at,
+ } as Task;📝 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.
| try { | |
| // Compare timestamps to detect stale server responses | |
| const serverUpdatedAt = new Date(data.updated_at); | |
| const optimisticUpdatedAt = new Date(task.updated_at); | |
| // If server data is newer or equal, use it completely | |
| if (serverUpdatedAt >= optimisticUpdatedAt) { | |
| return data; | |
| } | |
| // Server data appears stale - merge carefully | |
| // Keep server data for computed fields but preserve optimistic updates | |
| const mergedTask = { | |
| ...data, // Start with server data for computed fields | |
| ...updates, // Apply the original updates that were made | |
| updated_at: task.updated_at, // Keep the more recent optimistic timestamp | |
| }; | |
| return mergedTask; | |
| } catch (timestampError) { | |
| // If timestamp parsing fails, fall back to server data | |
| // This maintains existing behavior for edge cases | |
| console.warn('Failed to parse timestamps for smart merge, using server data:', timestampError); | |
| return data; | |
| } | |
| }); | |
| }); | |
| // Compare timestamps robustly; Date.parse returns NaN for invalid values (no exception) | |
| const serverMs = Date.parse(data.updated_at); | |
| const optimisticMs = Date.parse(task.updated_at); | |
| // Carry over client-only fields (e.g., priority) when not part of this update | |
| const carryClientOnly = (t: Task) => | |
| (t as any).priority !== undefined && (updates as any).priority === undefined | |
| ? { priority: (t as any).priority } | |
| : {}; | |
| // Fallback: if either timestamp is invalid, prefer server while keeping client-only fields | |
| if (!Number.isFinite(serverMs) || !Number.isFinite(optimisticMs)) { | |
| console.warn("Invalid updated_at; preferring server while keeping client-only fields"); | |
| return { ...data, ...carryClientOnly(task) } as Task; | |
| } | |
| if (serverMs >= optimisticMs) { | |
| // Server is fresher or equal: prefer server but keep client-only fields | |
| return { ...data, ...carryClientOnly(task) } as Task; | |
| } | |
| // Server appears stale: start from server, reapply updates, keep optimistic timestamp, and client-only fields | |
| return { | |
| ...data, | |
| ...carryClientOnly(task), | |
| ...updates, | |
| updated_at: task.updated_at, | |
| } as Task; | |
| }); | |
| }); |
🤖 Prompt for AI Agents
In archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts around
lines 140 to 167, replace the try/catch/new Date logic because new Date(...)
never throws and invalid dates produce NaN; instead call Date.parse(...) (or
getTime()) for both server.updated_at and task.updated_at, explicitly check
isNaN for each parsed value and branch accordingly: if both are valid compare
numerically and if server is newer return server data merged with any
client-only fields (e.g., priority) from task or updates so client-only fields
are never dropped; if client is newer return a merged object that preserves
optimistic updated_at and computed server fields; if either timestamp is invalid
fall back to server data but still copy client-only fields from task/updates
into the returned object.
- Set updated_at to current ISO timestamp during optimistic updates - Ensures smart merge logic triggers when server responses are stale - Fixes CodeRabbit review suggestion about timestamp comparison effectiveness 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
135-170: Replace try/catch date comparison; always preserve client-only fields (e.g., priority).new Date(...) never throws; invalid dates become NaN and your >= check will be false, incorrectly taking the “stale server” branch. Also, when the server is fresher, client-only fields like priority are dropped. Compare with Date.parse, validate explicitly, and carry client-only fields regardless of freshness.
Apply:
- try { - // Compare timestamps to detect stale server responses - const serverUpdatedAt = new Date(data.updated_at); - const optimisticUpdatedAt = new Date(task.updated_at); - - // If server data is newer or equal, use it completely - if (serverUpdatedAt >= optimisticUpdatedAt) { - return data; - } - - // Server data appears stale - merge carefully - // Keep server data for computed fields but preserve optimistic updates - const mergedTask = { - ...data, // Start with server data for computed fields - ...updates, // Apply the original updates that were made - updated_at: task.updated_at, // Keep the more recent optimistic timestamp - }; - - return mergedTask; - } catch (timestampError) { - // If timestamp parsing fails, fall back to server data - // This maintains existing behavior for edge cases - console.warn('Failed to parse timestamps for smart merge, using server data:', timestampError); - return data; - } + // Robust timestamp handling: Date.parse returns NaN for invalid values (no exception) + const serverMs = Date.parse((data as any).updated_at ?? ""); + const optimisticMs = Date.parse((task as any).updated_at ?? ""); + // Avoid clobbering fields with undefined (e.g., priority) + const cleanUpdates = Object.fromEntries( + Object.entries(updates as Record<string, unknown>).filter(([, v]) => v !== undefined), + ) as UpdateTaskRequest; + // Carry client-only fields (e.g., priority) if not part of this update + const carryClientOnly = + (task as any).priority !== undefined && (updates as any).priority === undefined + ? { priority: (task as any).priority } + : {}; + + // If either timestamp is invalid, prefer server while keeping client-only fields + if (!Number.isFinite(serverMs) || !Number.isFinite(optimisticMs)) { + console.warn("Invalid updated_at; preferring server while keeping client-only fields"); + return { ...data, ...carryClientOnly } as Task; + } + + if (serverMs >= optimisticMs) { + // Server is fresher or equal: prefer server but keep client-only fields + return { ...data, ...carryClientOnly } as Task; + } + + // Server appears stale: start from server, reapply updates, keep optimistic timestamp and client-only fields + return { + ...data, + ...carryClientOnly, + ...cleanUpdates, + updated_at: task.updated_at, + } as Task;
🧹 Nitpick comments (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
115-118: Avoid clobbering fields with undefined during optimistic merge.If updates contains keys with value undefined (common with Partial types), spreading can erase client-only fields (e.g., priority). Filter undefined before merging.
Apply:
- const nowIso = new Date().toISOString(); - return old.map((task) => - task.id === taskId ? { ...task, ...updates, updated_at: nowIso } : task, - ); + const nowIso = new Date().toISOString(); + const cleanUpdates = Object.fromEntries( + Object.entries(updates as Record<string, unknown>).filter(([, v]) => v !== undefined), + ) as UpdateTaskRequest; + return old.map((task) => + task.id === taskId ? { ...task, ...cleanUpdates, updated_at: nowIso } : task, + );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
archon-ui-main/src/features/**/{hooks,services}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TanStack Query for all data fetching (no prop drilling), with query key factory pattern and smart polling via useSmartPolling
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/features/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
archon-ui-main/src/features/**/*.{ts,tsx}: Implement optimistic updates with rollback in TanStack Query mutations
Use Biome formatting and lint rules in /src/features with 120-character lines, double quotes, and trailing commas
Implement HTTP polling patterns: 1–2s for active ops, 5–10s for background, smart pausing on tab inactivity, use ETag caching where applicable
archon-ui-main/src/features/**/*.{ts,tsx}: Use Radix UI primitives for UI in /features; place primitives under src/features/ui/primitives
Use TanStack Query for all data fetching in /features (no manual fetch chains in components)
Apply Tron-inspired glassmorphism styling using Tailwind classes in features UI
Use vertical slice structure: each feature owns its components/hooks/types/services; nest sub-features by domain
Run and address Biome recommended lint rules in features
No prop drilling; use TanStack Query and context/hooks for state/data flow
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use state naming conventions: is[Action]ing for loading, [resource]Error for errors, selected[Resource] for selections
archon-ui-main/src/**/*.{ts,tsx}: WebSocket event failures should be logged and not crash serving other clients
External API calls should retry with exponential backoff and ultimately fail with a clear, specific message
Include actionable context in frontend error logs/messages (what was attempted, relevant IDs/URLs)
Never return null to signal failure in async/data flows; throw errors with details
Use polling (HTTP) with provided hooks (usePolling, useDatabaseMutation, useProjectMutation); Socket.IO is removed
State naming: is[Action]ing for loading, [resource]Error for errors, selected[Resource] for selections
Persist theme choice in localStorage and respect Tailwind dark mode classes across components
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/features/**/tasks/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use task status values directly from the database: todo, doing, review, done (no UI mapping)
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/features/**/*.{ts,tsx,json}
📄 CodeRabbit inference engine (AGENTS.md)
Adhere to Biome settings in /features: 2-space formatting, 80-char line width, import sorting/grouping
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
archon-ui-main/src/**/*.{ts,tsx,py}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid comment keywords like LEGACY, CHANGED, REMOVED; write comments that document current functionality only
Files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Implement optimistic updates with rollback in TanStack Query mutations
📚 Learning: 2025-09-06T20:04:08.111Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/*.{ts,tsx} : Implement optimistic updates with rollback in TanStack Query mutations
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-09-06T20:04:08.111Z
Learnt from: CR
PR: coleam00/Archon#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-06T20:04:08.111Z
Learning: Applies to archon-ui-main/src/features/**/tasks/**/*.{ts,tsx} : Use task status values directly from the database: todo, doing, review, done (no UI mapping)
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
📚 Learning: 2025-09-06T20:05:27.061Z
Learnt from: CR
PR: coleam00/Archon#0
File: AGENTS.md:0-0
Timestamp: 2025-09-06T20:05:27.061Z
Learning: Applies to archon-ui-main/src/services/**/*.ts : Use GET /api/projects/{id}/tasks (not getTasks) for project tasks
Applied to files:
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts
🔇 Additional comments (1)
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts (1)
115-118: LGTM: optimistic timestamp bump is correct.Setting a single nowIso and applying it during the optimistic merge is the right move to order client-vs-server writes.
|
Ready to Merge |
|
solved in #695 replaced date matching with uuid matching for all optimistic updates, standardized across codebase, closing this one |
|
false positive, this is still an issue, but its flaky |
|
Found the actual issue, the etag did not send the description field, so the description was sent as stale fixed in #698 closing this as its not needed |
Update from v0.2.45 to v0.2.74. Notable additions: - 6 new hook events: Elicitation, ElicitationResult, ConfigChange, WorktreeCreate, WorktreeRemove, InstructionsLoaded (21 total) - thinking/effort options (replaces deprecated maxThinkingTokens) - maxBudgetUsd per-session cost cap - persistSession option to skip disk transcript writes - agents/AgentDefinition for programmatic subagent definitions - sandbox settings for OS-level isolation - spawnClaudeCodeProcess for custom process spawning - Task/hook lifecycle streaming events - createSdkMcpServer for in-process MCP tools - unstable_v2 multi-turn session API (alpha) - betas flag for 1M context window All existing tests pass, no breaking changes.
Update from v0.2.45 to v0.2.74. Notable additions: - 6 new hook events: Elicitation, ElicitationResult, ConfigChange, WorktreeCreate, WorktreeRemove, InstructionsLoaded (21 total) - thinking/effort options (replaces deprecated maxThinkingTokens) - maxBudgetUsd per-session cost cap - persistSession option to skip disk transcript writes - agents/AgentDefinition for programmatic subagent definitions - sandbox settings for OS-level isolation - spawnClaudeCodeProcess for custom process spawning - Task/hook lifecycle streaming events - createSdkMcpServer for in-process MCP tools - unstable_v2 multi-turn session API (alpha) - betas flag for 1M context window All existing tests pass, no breaking changes.
Update from v0.2.45 to v0.2.74. Notable additions: - 6 new hook events: Elicitation, ElicitationResult, ConfigChange, WorktreeCreate, WorktreeRemove, InstructionsLoaded (21 total) - thinking/effort options (replaces deprecated maxThinkingTokens) - maxBudgetUsd per-session cost cap - persistSession option to skip disk transcript writes - agents/AgentDefinition for programmatic subagent definitions - sandbox settings for OS-level isolation - spawnClaudeCodeProcess for custom process spawning - Task/hook lifecycle streaming events - createSdkMcpServer for in-process MCP tools - unstable_v2 multi-turn session API (alpha) - betas flag for 1M context window All existing tests pass, no breaking changes.
Problem Statement
Issue: Task description and priority updates disappear after page refresh, creating a poor user experience where changes appear to be saved but are actually lost.
Root Cause Analysis
Through systematic code analysis, I identified the issue in the TanStack Query optimistic update flow:
Code Evidence:
useTaskQueries.ts:132-136Solution: Smart Merge Strategy
Implemented timestamp-based smart merging that preserves user changes when server data appears stale:
Algorithm
updated_atvs optimisticupdated_atImplementation
Impact & Benefits
✅ Fixes Multiple Issues
✅ Maintains Reliability
useTaskQueries.test.ts✅ Performance Benefits
Testing
Automated Tests ✅
Manual Testing Scenarios
Technical Notes
Why Priority Was Also Affected
Priority is currently frontend-only and never sent to the backend:
TaskPriority.tsx: "Priority is currently frontend-only... purely for visual categorization"buildTaskUpdates()doesn't include priority fieldFuture Considerations
Related Files Changed
archon-ui-main/src/features/projects/tasks/hooks/useTaskQueries.ts- Core fixDeployment Safety
Summary by CodeRabbit