Refactor Perpetua frontend to Valora with UI enhancements - #140
Conversation
Code Review: Valora Frontend RefactorOverall this is a well-structured rewrite. The migration from Redux thunks to TanStack Query is a clear improvement — data fetching is now co-located with the components that need it, and the removal of ~900 lines of asset-manager plumbing significantly reduces complexity. Below is detailed feedback. Bugs
// useUpdateShelfMetadata (~line 44)
const result = await actor!.update_shelf_metadata(shelfId, t, d);
unwrapResult(result); // ← return value discarded
// useAddItem (~line 80)
const result = await actor!.add_item_to_shelf(...);
unwrapResult(result); // ← same issueBoth
const key = Object.keys(err)[0];
throw new Error(key || "Unknown error");When a variant error comes back (e.g., const key = Object.keys(err)[0];
const val = err[key];
const detail = val !== null && val !== undefined ? `: ${JSON.stringify(val)}` : "";
throw new Error(key ? `${key}${detail}` : "Unknown error");
if ("Nft" in content) return content.Nft!;
if ("Markdown" in content) return content.Markdown!;
return content.Shelf!;The last line asserts if ("Shelf" in content && content.Shelf !== undefined) return content.Shelf;
throw new Error(`Unknown item content variant: ${JSON.stringify(content)}`);Actor Non-null Assertions in MutationsEvery const result = await actor!.store_shelf(title, desc, [], tagList);The if (!actor) throw new Error("Not authenticated");Minor Code Quality Notes
Input ValidationUser-supplied strings (tags, titles, descriptions, markdown content) flow from form inputs directly to canister calls with no visible length or format validation in the UI layer. A few guardrails to consider:
The backend likely enforces these, but early client-side validation improves UX (instant feedback vs. a rejected canister call). Test CoverageThe two updated test files only swap the
...would meaningfully reduce regression risk. Dependency Changes
Summary
The core approach is solid. The two missing |
|
test |
|
Review comment from automated code review agent: the full structured review will follow in a subsequent comment due to formatting constraints. |
|
Code Review: Perpetua to Valora Refactor Overall this is a well-executed migration from Redux thunks to TanStack Query. The separation of concerns is clean, optimistic updates are implemented correctly, and the architecture is much easier to follow. Issues below should be addressed before merge. BUGS 1. unwrapResult return values dropped in 3 mutations (useMutations.ts) useRemoveItem, useSetItemOrder, and useReorderProfileShelf call unwrapResult(result) but discard the return value. Contrast with useCreateShelf and useUpdateShelfMetadata which correctly return it. The mutationFn returns undefined for these three, so mutation.data is always undefined for callers. Fix: add 2. useAddItem does not invalidate feed queries (useMutations.ts) Adding an item updates the shelf updated_at timestamp, changing its position in the recency feed. The feed will show stale ordering until the 30-second staleTime expires. The onSuccess handler should also call: qc.invalidateQueries({ queryKey: valoraKeys.recentFeed() });PERFORMANCE 3. N+1 queries in ShelfItemCard (components/ShelfItemCard.tsx) Each nested-shelf item fires an independent useShelf(shelfId) query. A shelf with many shelf-type items generates N parallel canister calls. React Query deduplicates identical keys, but distinct shelf IDs each pay full round-trip cost. Consider passing title and item count as props from the parent when available, falling back to useShelf only when needed. 4. getId must be stable in useReorder (hooks/useReorder.ts) onDragEnd has getId in its useCallback dependency array. If the caller passes an inline arrow function, onDragEnd is recreated every render — with dnd-kit/core this can cause dropped drag events on slower devices. The hook should document that getId must be a stable reference (useCallback at the call site), or stabilize it internally with useRef. 5. Infinite query pages accumulate without bound (hooks/useFeeds.ts) useRecentFeed and useStorylineFeed have no maxPages limit (TanStack Query v5 supports this). After extended scrolling the cache grows indefinitely. Consider maxPages: 5 with a matching getPreviousPageParam. CODE QUALITY 6. Empty-string description coerced to null in optimistic update (useMutations.ts) In useUpdateShelfMetadata the optimistic update uses 7. Separator imported but unused (components/ValoraLayout.tsx) 8. Commented-out extraReducers cases (arweaveAssetsSlice.ts) Commented-out addCase handlers for checkAssetsAvailability and checkAssetAvailability were left in the file. Since the corresponding thunks are deleted in this PR, these should be removed. TYPE SAFETY 9. Non-null assertions on discriminated union (utils.ts) In getItemContentValue, the TEST COVERAGE No tests were added for the Valora feature. Most valuable areas to cover:
The existing tests were correctly updated to reflect removal of the actor parameter from updateTransactions and toggleSort. MINOR
Overall the architecture is solid and the asset manager / ICP assets cleanup is a welcome simplification. Items 1 and 2 are highest priority to fix before merge. |
Code Review: Valora Frontend Rewrite (PR #140)Overall this is a well-executed migration from Redux thunks to TanStack Query. The separation of concerns is clear — React Query owns server state, Redux owns UI state — and the code is noticeably cleaner than what it replaces. The query key factory, optimistic updates, and Bugs / Correctness1. Type-unsafe cursor cast in const cursor = pageParam ? [pageParam] : [];
const result = await actor!.get_shelves_by_tag(tag!, { cursor: cursor as any, limit: 20n });
const cursor: [] | [bigint] = pageParam ? [BigInt(pageParam)] : [];This could cause silent failures when paginating tag search results. 2. Both functions treat export function getItemContentValue(content: { Nft?: string; Markdown?: string; Shelf?: string }): string {
if ("Nft" in content) return content.Nft!;
if ("Markdown" in content) return content.Markdown!;
return content.Shelf!; // returns undefined! if type is unknown
}If a new Performance3. Sequential mutations for shelf reordering ( for (let i = 0; i < reordered.length; i++) {
await reorderShelf.mutateAsync({ shelfId: shelf.shelfId, ... });
}This makes N sequential round-trips to reorder N shelves. For a user with 20 shelves this will be noticeably slow. If the canister supports a bulk reorder call, use it. If not, Inconsistency4.
Minor Issues5. Stale data risk in const enterEditMode = useCallback(() => {
setEditedItems([...items]); // snapshot taken here
setIsEditMode(true);
}, [items]);If 6. mutationFn: async ({ shelfId, publicEditing }) => {
const result = await actor.toggle_shelf_public_access(shelfId, publicEditing);
unwrapResult(result); // return value dropped
},All other mutations 7. Empty string query key fallback (multiple hooks)
What's Good
|
Summary
core/features/valora/using TanStack Query instead of Redux thunksmode)
following, drag-and-drop reordering, permissions