Files
backnotprop__plannotator/packages/editor/sourceDocumentClient.ts
Michael Ramos 195328f7a6 Persist saved annotate file edits in drafts (#936)
* Persist saved annotate file edits in drafts

* Handle stale saved file edit context

* Test source edit conflict actions

* Return source metadata for single-file docs

* Fix saved file edit conflict races

* Handle deleted source files in annotate edits

* Tighten annotate missing-file recovery

* Reset edit state for missing file reopen

* Tighten source edit restore path handling

* Harden source edit recovery paths

* Harden source edit disk reconciliation

* Fix live file tree startup delay

* Document file tree watcher startup ordering

* Preserve source save through missing files and symlinks

* Harden missing source file recovery

* Tighten annotate source edit boundaries
2026-06-18 21:31:25 -07:00

65 lines
2.3 KiB
TypeScript

import type { SourceSaveCapability } from '@plannotator/shared/source-save';
type EnabledSourceSaveCapability = Extract<SourceSaveCapability, { enabled: true }>;
export type SourceSaveProbeResult =
| { status: 'ok'; sourceSave: EnabledSourceSaveCapability }
| { status: 'missing' }
| { status: 'unavailable' };
interface SourceDocumentResponse {
markdown?: string;
sourceSave?: SourceSaveCapability;
renderAs?: 'markdown' | 'html';
}
type SourceDocumentFetchResult =
| { status: 'ok'; data: SourceDocumentResponse }
| { status: 'missing' }
| { status: 'unavailable' };
export interface SourceDocumentSnapshot {
markdown: string;
sourceSave: EnabledSourceSaveCapability;
}
export type SourceDocumentSnapshotResult =
| { status: 'ok'; snapshot: SourceDocumentSnapshot }
| { status: 'missing' }
| { status: 'unavailable' };
async function fetchSourceDocument(path: string): Promise<SourceDocumentFetchResult> {
try {
const res = await fetch(`/api/doc?path=${encodeURIComponent(path)}`);
if (res.status === 404) return { status: 'missing' };
if (!res.ok) return { status: 'unavailable' };
return { status: 'ok', data: await res.json() as SourceDocumentResponse };
} catch {
return { status: 'unavailable' };
}
}
export async function probeSourceSave(path: string): Promise<SourceSaveProbeResult> {
const result = await fetchSourceDocument(path);
if (result.status !== 'ok') return { status: result.status };
const { sourceSave } = result.data;
if (sourceSave?.enabled) return { status: 'ok', sourceSave };
if (sourceSave?.enabled === false && sourceSave.reason === 'missing-file') {
return { status: 'missing' };
}
return { status: 'unavailable' };
}
export async function fetchSourceDocumentSnapshot(path: string): Promise<SourceDocumentSnapshotResult> {
const result = await fetchSourceDocument(path);
if (result.status !== 'ok') return { status: result.status };
const { markdown, renderAs, sourceSave } = result.data;
if (sourceSave?.enabled === false && sourceSave.reason === 'missing-file') {
return { status: 'missing' };
}
if (renderAs === 'html' || typeof markdown !== 'string' || !sourceSave?.enabled) return { status: 'unavailable' };
return { status: 'ok', snapshot: { markdown, sourceSave } };
}