mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
c2950e709f
Fixes from the 0.27.9 pre-release review. Servers: an unreadable rendered-HTML root falls back to the startup snapshot on both runtimes with a once-per-process warning instead of hanging (Pi) or answering 500 (Bun); the version diff is recomputed against current bytes on reload and carried through the in-app Refresh instead of being dropped, with no history write on a GET. Client: a Refresh action on the compact touch shell; HtmlSurfaceControls renders Refresh independently of the eye; the dead HtmlSurfaceActions removed. Threading: one linear, cycle-safe reply resolution shared by the annotations panel, its sort, and the export (5,000-chain tests), PATCH ingest on both runtimes rejects self-references and cycles, nothing is ever dropped from feedback. WebMCP and viewer hygiene: bounded tombstone and request memories, per-instance minted ids, nudge id caps, waiter cleanup on unmount, a shared retry epoch for diagram blocks. Docs: HTML Refresh documented, the WebMCP design pointer fixed, marketing pages updated. AI-assisted (Claude) under maintainer direction.
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import { useCallback } from 'react';
|
|
import { toast } from 'sonner';
|
|
import {
|
|
useHtmlRefresh as usePublishedHtmlRefresh,
|
|
type HtmlRefreshSnapshot,
|
|
} from '@plannotator/ui/hooks/useHtmlRefresh';
|
|
import { fetchHtmlDocumentSnapshot, type HtmlVersionDiffFields } from '../sourceDocumentClient';
|
|
|
|
/** What a refresh hands the app: the bytes plus, for the root document, the
|
|
* version-diff fields the server recomputed against them. */
|
|
export interface HtmlRefreshedDocument extends HtmlVersionDiffFields {
|
|
rawHtml: string;
|
|
}
|
|
|
|
interface UseHtmlRefreshOptions {
|
|
enabled: boolean;
|
|
activePath: string | null;
|
|
onSnapshot: (document: HtmlRefreshedDocument) => void;
|
|
/** The ids the remounted viewer could not re-anchor, once per refresh. */
|
|
onUnanchored?: (missingIds: string[]) => void;
|
|
}
|
|
|
|
interface UseHtmlRefreshResult {
|
|
canRefresh: boolean;
|
|
isRefreshing: boolean;
|
|
reloadGeneration: number;
|
|
refresh: () => Promise<void>;
|
|
reportAnnotationRestore: (missingIds: string[]) => void;
|
|
}
|
|
|
|
/**
|
|
* Plannotator's binding of the published `useHtmlRefresh`: the snapshot
|
|
* comes from `/api/doc` through `fetchHtmlDocumentSnapshot` (which, for the
|
|
* session's root document, also carries the recomputed version diff), URL
|
|
* sessions (http(s) paths) cannot refresh, and every outcome toasts.
|
|
*/
|
|
export function useHtmlRefresh({
|
|
enabled,
|
|
activePath,
|
|
onSnapshot,
|
|
onUnanchored,
|
|
}: UseHtmlRefreshOptions): UseHtmlRefreshResult {
|
|
const canRefresh = enabled && !!activePath && !/^https?:\/\//i.test(activePath);
|
|
|
|
const fetchSnapshot = useCallback(async (path: string | null): Promise<HtmlRefreshSnapshot<HtmlVersionDiffFields>> => {
|
|
const result = await fetchHtmlDocumentSnapshot(path ?? '');
|
|
if (result.status !== 'ok') return { status: result.status };
|
|
const { rawHtml, diffHtml, previousPlan, versionInfo } = result.snapshot;
|
|
return { status: 'ok', rawHtml, diffHtml, previousPlan, versionInfo };
|
|
}, []);
|
|
|
|
const handleSnapshot = useCallback((rawHtml: string, snapshot: HtmlVersionDiffFields) => {
|
|
onSnapshot({
|
|
rawHtml,
|
|
diffHtml: snapshot.diffHtml,
|
|
previousPlan: snapshot.previousPlan,
|
|
versionInfo: snapshot.versionInfo,
|
|
});
|
|
}, [onSnapshot]);
|
|
|
|
const handleResult = useCallback((result: 'refreshed' | 'missing' | 'unavailable') => {
|
|
if (result === 'missing') {
|
|
toast.error('HTML file no longer exists', {
|
|
description: 'The current rendered version remains open.',
|
|
});
|
|
} else if (result === 'unavailable') {
|
|
toast.error('Could not refresh HTML', {
|
|
description: 'The current rendered version remains open. Try again in a moment.',
|
|
});
|
|
} else {
|
|
toast.success('Refreshed HTML from disk');
|
|
}
|
|
}, []);
|
|
|
|
const handleUnanchored = useCallback((missingIds: string[]) => {
|
|
onUnanchored?.(missingIds);
|
|
if (missingIds.length === 0) return;
|
|
toast(`${missingIds.length} annotation${missingIds.length === 1 ? ' no longer matches' : 's no longer match'} the HTML`, {
|
|
description: 'Their comments remain available in the annotations panel.',
|
|
duration: 5000,
|
|
});
|
|
}, [onUnanchored]);
|
|
|
|
return usePublishedHtmlRefresh<HtmlVersionDiffFields>({
|
|
enabled: canRefresh,
|
|
documentKey: activePath,
|
|
fetchSnapshot,
|
|
onSnapshot: handleSnapshot,
|
|
onUnanchored: handleUnanchored,
|
|
onResult: handleResult,
|
|
});
|
|
}
|