Skip to content

Commit 098c583

Browse files
Oba-Oneclaude
andcommitted
fix(client): render work view immediately, load metadata lazily
The work detail view was gated entirely on metadata loading — when metadata required an IPFS fetch (up to 30s timeout), users saw a full skeleton even though work title, status, garden info, and media URLs were already cached. Now the view renders immediately with all available data. Only the metadata details section shows skeleton placeholders while loading, so images and other content appear instantly on navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 183811f commit 098c583

3 files changed

Lines changed: 44 additions & 32 deletions

File tree

packages/client/src/components/Features/Work/WorkView.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type WorkViewProps = {
2626
actionTitle: string;
2727
media?: string[];
2828
details: Array<{ label: string; value: string; icon?: IconComponent | null }>;
29+
/** When true, shows skeleton placeholders for details instead of the actual cards */
30+
isDetailsLoading?: boolean;
2931
headerIcon?: IconComponent | null;
3032
primaryActions?: WorkViewAction[]; // shown near header or under details
3133
feedbackSection?: React.ReactNode; // optional feedback input section
@@ -51,6 +53,7 @@ export const WorkView: React.FC<WorkViewProps> = ({
5153
actionTitle,
5254
media = [],
5355
details,
56+
isDetailsLoading = false,
5457
headerIcon: HeaderIcon,
5558
primaryActions = [],
5659
feedbackSection,
@@ -122,16 +125,24 @@ export const WorkView: React.FC<WorkViewProps> = ({
122125
Icon={RiExternalLinkLine}
123126
/>
124127

125-
{details
126-
.filter((d) => d.value && d.value.trim().length > 0)
127-
.map((d) => (
128-
<FormCard
129-
key={d.label}
130-
label={d.label}
131-
value={d.value}
132-
Icon={d.icon ?? RiExternalLinkLine}
133-
/>
134-
))}
128+
{isDetailsLoading ? (
129+
<div className="space-y-2">
130+
{Array.from({ length: 2 }).map((_, i) => (
131+
<div key={`detail-loading-${i}`} className="h-12 bg-bg-weak-50 rounded-lg animate-pulse" />
132+
))}
133+
</div>
134+
) : (
135+
details
136+
.filter((d) => d.value && d.value.trim().length > 0)
137+
.map((d) => (
138+
<FormCard
139+
key={d.label}
140+
label={d.label}
141+
value={d.value}
142+
Icon={d.icon ?? RiExternalLinkLine}
143+
/>
144+
))
145+
)}
135146

136147
{feedbackSection}
137148

packages/client/src/views/Home/Garden/Work.tsx

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,6 @@ export const GardenWork: React.FC = () => {
515515
</article>
516516
);
517517

518-
const isMetadataLoading = metadataStatus === "loading" || metadataStatus === "idle";
519518
const hasMedia = Array.isArray(work.media) && work.media.length > 0;
520519
const resolvedActionTitle =
521520
actionTitle ??
@@ -824,27 +823,22 @@ export const GardenWork: React.FC = () => {
824823
<article>
825824
<TopNav onBackClick={handleBack} overlay />
826825
<div className="padded pt-20">
827-
{isMetadataLoading ? (
828-
<WorkViewSkeleton showMedia showActions={false} numDetails={3} />
829-
) : (
830-
<>
831-
<WorkViewSection
832-
garden={garden}
833-
work={work}
834-
workMetadata={workMetadata}
835-
viewingMode={viewingMode}
836-
actionTitle={resolvedActionTitle}
837-
effectiveStatus={effectiveStatus}
838-
onDownloadData={handleDownloadData}
839-
onDownloadMedia={hasMedia ? handleDownloadMedia : undefined}
840-
onShare={handleShare}
841-
onViewAttestation={canViewAttestation ? handleViewAttestation : undefined}
842-
footer={retryFooter || approvalFooter || successFooter}
843-
reserveFooterSpace={Boolean(retryFooter || approvalFooter || successFooter)}
844-
footerSpacerClassName="h-[calc(112px+env(safe-area-inset-bottom))]"
845-
/>
846-
</>
847-
)}
826+
<WorkViewSection
827+
garden={garden}
828+
work={work}
829+
workMetadata={workMetadata}
830+
metadataStatus={metadataStatus}
831+
viewingMode={viewingMode}
832+
actionTitle={resolvedActionTitle}
833+
effectiveStatus={effectiveStatus}
834+
onDownloadData={handleDownloadData}
835+
onDownloadMedia={hasMedia ? handleDownloadMedia : undefined}
836+
onShare={handleShare}
837+
onViewAttestation={canViewAttestation ? handleViewAttestation : undefined}
838+
footer={retryFooter || approvalFooter || successFooter}
839+
reserveFooterSpace={Boolean(retryFooter || approvalFooter || successFooter)}
840+
footerSpacerClassName="h-[calc(112px+env(safe-area-inset-bottom))]"
841+
/>
848842

849843
{metadataStatus === "error" && (
850844
<div className="mt-4 rounded-xl border border-error-light bg-error-lighter px-4 py-3 flex items-start gap-3">

packages/client/src/views/Home/Garden/WorkViewSection.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@ import { WorkView, type WorkViewAction } from "@/components/Features/Work";
2323

2424
type ViewingMode = "operator" | "gardener" | "viewer";
2525

26+
type MetadataStatus = "idle" | "loading" | "success" | "error";
27+
2628
type WorkViewSectionProps = {
2729
garden?: Garden;
2830
work: Work;
2931
workMetadata: WorkMetadata | null;
32+
metadataStatus?: MetadataStatus;
3033
viewingMode: ViewingMode;
3134
actionTitle: string;
3235
effectiveStatus: WorkDisplayStatus;
@@ -168,6 +171,7 @@ export const WorkViewSection: React.FC<WorkViewSectionProps> = ({
168171
garden,
169172
work,
170173
workMetadata,
174+
metadataStatus = "success",
171175
viewingMode,
172176
actionTitle,
173177
effectiveStatus,
@@ -387,6 +391,8 @@ export const WorkViewSection: React.FC<WorkViewSectionProps> = ({
387391
return items;
388392
}, [metadataDetails, workFeedback, intl]);
389393

394+
const isDetailsLoading = metadataStatus === "loading" || metadataStatus === "idle";
395+
390396
return (
391397
<WorkView
392398
title={getTitle()}
@@ -395,6 +401,7 @@ export const WorkViewSection: React.FC<WorkViewSectionProps> = ({
395401
actionTitle={actionTitle}
396402
media={media}
397403
details={allDetails}
404+
isDetailsLoading={isDetailsLoading}
398405
headerIcon={RiCheckDoubleFill}
399406
primaryActions={primaryActions}
400407
footer={footer}

0 commit comments

Comments
 (0)