Skip to content

Commit 171691c

Browse files
authored
fix(annotate): include original file annotations when submitting from linked doc view (#536)
* fix(annotate): include original file annotations when submitting from linked doc view When using `plannotator annotate` on a file with markdown links, annotations on the original file were silently dropped if the user clicked "Send Annotations" while viewing a linked document. The root cause was that `getDocAnnotations()` only read from `docCache` (previously-visited linked docs) and the current linked doc — it never read from `savedPlanState`, where the original file's annotations are stashed during navigation. This adds a `sourceFilePath` option to `useLinkedDoc` so the hook can key the stashed annotations in the returned Map. Also updates `docAnnotationCount` in `open()` so button visibility and exit warnings reflect reality immediately, not only after calling `back()`. Closes #535 For provenance purposes, this commit was AI assisted. * fix(annotate): include docCache in count when re-entering linked doc navigation The first branch of open() set docAnnotationCount from only the original file's annotations, ignoring any linked docs already cached from prior navigation. After original → linkedA → back → linkedB, the count dropped to 0 if the original had no annotations, hiding the Send Annotations button despite linkedA's annotations sitting in docCache. Also adds defensive spread copies for savedPlanState arrays in getDocAnnotations() to match the hook's existing pattern. For provenance purposes, this commit was AI assisted. * fix(annotate): exclude destination doc from docAnnotationCount to prevent double-counting When navigating to a previously-cached linked doc, the destination's cached annotations were included in docAnnotationCount AND loaded into allAnnotations, causing the exit warning to report an inflated count. Skip the destination filepath when summing the non-active total. For provenance purposes, this commit was AI assisted. * fix(annotate): treat backlinks to source file as back() navigation When a linked document contains a link back to the source file (e.g., original.md → design.md → backlink to original.md), opening it as a linked doc created two competing Map entries for the same filepath in getDocAnnotations(). The empty linked-doc entry overwrote the stashed annotations, silently dropping them. Now detects when the resolved destination matches sourceFilePath and routes through back() instead, which caches the current linked doc and restores the source file with its annotations intact. For provenance purposes, this commit was AI assisted.
1 parent 0287b96 commit 171691c

2 files changed

Lines changed: 45 additions & 3 deletions

File tree

packages/editor/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ const App: React.FC = () => {
134134
const [globalAttachments, setGlobalAttachments] = useState<ImageAttachment[]>([]);
135135
const [annotateMode, setAnnotateMode] = useState(false);
136136
const [annotateSource, setAnnotateSource] = useState<'file' | 'message' | 'folder' | null>(null);
137+
const [sourceFilePath, setSourceFilePath] = useState<string | undefined>();
137138
const [imageBaseDir, setImageBaseDir] = useState<string | undefined>(undefined);
138139
const [isLoading, setIsLoading] = useState(true);
139140
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -218,7 +219,7 @@ const App: React.FC = () => {
218219
const linkedDocHook = useLinkedDoc({
219220
markdown, annotations, selectedAnnotationId, globalAttachments,
220221
setMarkdown, setAnnotations, setSelectedAnnotationId, setGlobalAttachments,
221-
viewerRef, sidebar,
222+
viewerRef, sidebar, sourceFilePath,
222223
});
223224

224225
// Archive browser
@@ -554,6 +555,9 @@ const App: React.FC = () => {
554555
}
555556
if (data.filePath) {
556557
setImageBaseDir(data.mode === 'annotate-folder' ? data.filePath : data.filePath.replace(/\/[^/]+$/, ''));
558+
if (data.mode === 'annotate') {
559+
setSourceFilePath(data.filePath);
560+
}
557561
}
558562
if (data.sharingEnabled !== undefined) {
559563
setSharingEnabled(data.sharingEnabled);

packages/ui/hooks/useLinkedDoc.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface UseLinkedDocOptions {
2222
setGlobalAttachments: (att: ImageAttachment[]) => void;
2323
viewerRef: React.RefObject<ViewerHandle | null>;
2424
sidebar: { open: (tab?: SidebarTab) => void };
25+
/** Absolute path of the primary document — enables getDocAnnotations() to include
26+
* stashed original-file annotations when viewing a linked doc. */
27+
sourceFilePath?: string;
2528
}
2629

2730
interface SavedPlanState {
@@ -53,7 +56,7 @@ export interface UseLinkedDocReturn {
5356
dismissError: () => void;
5457
/** All linked doc annotations including the active doc's live state (keyed by filepath) */
5558
getDocAnnotations: () => Map<string, CachedDocState>;
56-
/** Reactive count of cached linked doc annotations (updates on back()) */
59+
/** Reactive count of annotations on non-active documents (updates on open() and back()) */
5760
docAnnotationCount: number;
5861
}
5962

@@ -71,6 +74,7 @@ export function useLinkedDoc(options: UseLinkedDocOptions): UseLinkedDocReturn {
7174
setGlobalAttachments,
7275
viewerRef,
7376
sidebar,
77+
sourceFilePath,
7478
} = options;
7579

7680
const [linkedDoc, setLinkedDoc] = useState<{ filepath: string } | null>(null);
@@ -109,6 +113,18 @@ export function useLinkedDoc(options: UseLinkedDocOptions): UseLinkedDocReturn {
109113
return;
110114
}
111115

116+
// Backlink detection: if a linked doc links back to the source file (e.g.,
117+
// original.md → design.md → link back to original.md), opening it as a linked
118+
// doc would create two competing Map entries for the same filepath in
119+
// getDocAnnotations(), and the empty linked-doc entry would overwrite the
120+
// stashed annotations. Instead, treat the backlink as a back() navigation —
121+
// the current linked doc gets cached and the source file restores with its
122+
// annotations intact.
123+
if (sourceFilePath && data.filepath === sourceFilePath && savedPlanState.current) {
124+
back();
125+
return;
126+
}
127+
112128
// Clear web-highlighter marks before swapping content to prevent React DOM mismatch
113129
viewerRef.current?.clearAllHighlights();
114130

@@ -120,12 +136,27 @@ export function useLinkedDoc(options: UseLinkedDocOptions): UseLinkedDocReturn {
120136
selectedAnnotationId,
121137
globalAttachments: [...globalAttachments],
122138
};
139+
let total = annotations.length + globalAttachments.length;
140+
for (const [fp, cached] of docCache.current.entries()) {
141+
if (fp === data.filepath!) continue; // destination becomes active — don't double-count
142+
total += cached.annotations.length + cached.globalAttachments.length;
143+
}
144+
setDocAnnotationCount(total);
123145
} else if (linkedDoc) {
124146
// Already viewing a linked doc — cache its annotations before moving on
125147
docCache.current.set(linkedDoc.filepath, {
126148
annotations: [...annotations],
127149
globalAttachments: [...globalAttachments],
128150
});
151+
let total = 0;
152+
for (const [fp, cached] of docCache.current.entries()) {
153+
if (fp === data.filepath!) continue; // destination becomes active — don't double-count
154+
total += cached.annotations.length + cached.globalAttachments.length;
155+
}
156+
if (savedPlanState.current) {
157+
total += savedPlanState.current.annotations.length + savedPlanState.current.globalAttachments.length;
158+
}
159+
setDocAnnotationCount(total);
129160
}
130161

131162
// Check cache for previous annotations on this file
@@ -219,14 +250,21 @@ export function useLinkedDoc(options: UseLinkedDocOptions): UseLinkedDocReturn {
219250

220251
const getDocAnnotations = useCallback((): Map<string, CachedDocState> => {
221252
const result = new Map(docCache.current);
253+
// Include stashed original-file annotations when viewing a linked doc
254+
if (linkedDoc && savedPlanState.current && sourceFilePath) {
255+
result.set(sourceFilePath, {
256+
annotations: [...savedPlanState.current.annotations],
257+
globalAttachments: [...savedPlanState.current.globalAttachments],
258+
});
259+
}
222260
if (linkedDoc) {
223261
result.set(linkedDoc.filepath, {
224262
annotations: [...annotations],
225263
globalAttachments: [...globalAttachments],
226264
});
227265
}
228266
return result;
229-
}, [linkedDoc, annotations, globalAttachments]);
267+
}, [linkedDoc, annotations, globalAttachments, sourceFilePath]);
230268

231269
return {
232270
isActive: linkedDoc !== null,

0 commit comments

Comments
 (0)