Skip to content

Commit 2003199

Browse files
committed
Show loader instead of not-found for mobile annotation deep-links
When deep-linking directly to an annotation on mobile before anything is cached, the document and its annotations take a few seconds to load. During that window the mobile Annotation sheet resolved the selected id against an empty annotation set and rendered "This annotation is no longer available.", wrongly implying the annotation was gone. Thread the document loader's loading flag into MobileAnnotationDetail and render a subtle spinning loader while loading is true and the annotation is not yet resolved. The not-found message now only appears once loading settles and the annotation is still unresolved.
1 parent 7945fc7 commit 2003199

3 files changed

Lines changed: 69 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111

12+
- **Mobile annotation deep-link showed "no longer available" instead of a loader during the initial uncached load (2026-06).**
13+
Navigating directly to an annotation on mobile (e.g. `cite.opensource.legal/d/...?ann=<id>`)
14+
before anything is cached takes a few seconds while the document and its
15+
annotations are fetched. During that window the mobile "Annotation" sheet
16+
(`frontend/src/components/knowledge_base/document/layouts/mobile/MobileAnnotationDetail.tsx`)
17+
resolved the selected id against an empty annotation set and rendered
18+
"This annotation is no longer available." — wrongly implying the annotation
19+
was gone. The component had no access to a loading signal, so it could not
20+
distinguish "still fetching" from "not found". Fix: thread the document
21+
loader's `loading` flag from `MobileDocumentLayout.tsx` into
22+
`MobileAnnotationDetail`; while `loading` is true and the annotation is not
23+
yet resolved, render a subtle spinning loader ("Loading annotation…") instead
24+
of the not-found message. The not-found state now only appears once loading
25+
settles and the annotation is still unresolved.
26+
1227
- **Deep-research and conversation-memory Celery tasks were never registered on the worker (2026-06).**
1328
`run_deep_research` (`opencontractserver/tasks/research_tasks.py`) and the
1429
memory tasks `check_conversations_for_curation` / `curate_corpus_memory`

‎frontend/src/components/knowledge_base/document/layouts/MobileDocumentLayout.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ export const MobileDocumentLayout: React.FC<DocumentLayoutProps> = (props) => {
480480
title="Annotation"
481481
onClose={() => setSelectedAnnotations([])}
482482
>
483-
<MobileAnnotationDetail readOnly={readOnly} />
483+
<MobileAnnotationDetail readOnly={readOnly} loading={loading} />
484484
</MobileSheet>
485485
</Root>
486486
</FullScreenModal>

‎frontend/src/components/knowledge_base/document/layouts/mobile/MobileAnnotationDetail.tsx‎

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useMemo } from "react";
2-
import styled from "styled-components";
2+
import styled, { keyframes } from "styled-components";
3+
import { Loader2 } from "lucide-react";
34

45
import { OS_LEGAL_COLORS } from "../../../../../assets/configurations/osLegalStyles";
56
import { MOBILE_RADIUS, MOBILE_SHADOW } from "./mobileTheme";
@@ -22,6 +23,34 @@ const EmptyState = styled.div`
2223
text-align: center;
2324
`;
2425

26+
const spin = keyframes`
27+
to {
28+
transform: rotate(360deg);
29+
}
30+
`;
31+
32+
/**
33+
* Subtle, calm loading affordance shown while the document (and its
34+
* annotations) are still being fetched — e.g. a deep-link straight to an
35+
* annotation before anything is cached. A quietly spinning icon over the
36+
* sheet's white surface, never a heavy full-bleed spinner.
37+
*/
38+
const LoadingState = styled.div`
39+
display: flex;
40+
flex-direction: column;
41+
align-items: center;
42+
gap: 12px;
43+
padding: 40px 16px;
44+
font-size: 14px;
45+
color: ${OS_LEGAL_COLORS.textSecondary};
46+
text-align: center;
47+
48+
svg {
49+
color: ${OS_LEGAL_COLORS.accent};
50+
animation: ${spin} 0.9s linear infinite;
51+
}
52+
`;
53+
2554
/**
2655
* Mobile frame for the shared {@link HighlightItem} detail.
2756
*
@@ -66,6 +95,13 @@ const Card = styled.div`
6695
interface MobileAnnotationDetailProps {
6796
/** Read-only mode disables editing capabilities (delete). */
6897
readOnly: boolean;
98+
/**
99+
* True while the document (and its annotations) are still being fetched.
100+
* Threaded from the document loader so a deep-link straight to an annotation
101+
* — before anything is cached — shows a loader instead of prematurely
102+
* claiming the annotation is gone.
103+
*/
104+
loading: boolean;
69105
}
70106

71107
/**
@@ -85,6 +121,7 @@ interface MobileAnnotationDetailProps {
85121
*/
86122
export const MobileAnnotationDetail: React.FC<MobileAnnotationDetailProps> = ({
87123
readOnly,
124+
loading,
88125
}) => {
89126
const { selectedAnnotations } = useAnnotationSelection();
90127
const allAnnotations = useAllAnnotations();
@@ -99,12 +136,25 @@ export const MobileAnnotationDetail: React.FC<MobileAnnotationDetailProps> = ({
99136
const annotation = useMemo(
100137
() =>
101138
[...allAnnotations, ...(structuralAnnotations || [])].find(
102-
(a) => a.id === selectedId
139+
(a) => a.id === selectedId,
103140
) ?? null,
104-
[allAnnotations, structuralAnnotations, selectedId]
141+
[allAnnotations, structuralAnnotations, selectedId],
105142
);
106143

107144
if (!annotation) {
145+
// While the document/annotations are still loading (e.g. a deep-link
146+
// straight to an annotation before anything is cached), the selected id
147+
// simply hasn't arrived yet — show a loader rather than wrongly reporting
148+
// the annotation as gone. Only once loading settles and it's still
149+
// unresolved do we treat it as unavailable.
150+
if (loading) {
151+
return (
152+
<LoadingState>
153+
<Loader2 size={22} aria-hidden />
154+
Loading annotation…
155+
</LoadingState>
156+
);
157+
}
108158
return <EmptyState>This annotation is no longer available.</EmptyState>;
109159
}
110160

0 commit comments

Comments
 (0)