6247585b66
PR-1 'core' of #370: introduces page_history.kind ('manual'|'agent'|'idle'| 'boundary'; legacy null = autosave) and rebuilds the snapshot triggers around a three-tier intentionality model. Draft durability (pages/ydoc hocuspocus autosave) is unchanged; only the frequency and labelling of history points change. - Migration 20260705T120000: page_history.kind nullable varchar(20), no default. - Manual Save: one stateless 'save-version' path for human AND agent; kind is derived SERVER-SIDE from the signed context.actor (never the payload), readOnly connections rejected, the fresh ydoc runs through the existing store path (no REST race), then broadcasts version.saved. - Idle-flush: trailing debounce (one BullMQ job per page, remove-then-readd) with IDLE_INTERVAL_USER=60m / AGENT=15m AND a max-wait ceiling (IDLE_MAX_WAIT_USER=10m / AGENT=5m) so a continuous editing session can't starve the autosnapshot (review round-1 WARNING). - Boundary: generalized from the user→agent special-case to ANY lastUpdatedSource transition (user↔agent↔git), same isDeepStrictEqual gate — covers git-sync free. - Removed the agent delay=0 fast path and the old HISTORY_FAST_* constants; the agent joins the common idle pipeline. - Promote-not-dup: a manual save on unchanged content promotes the latest autosave's kind in place (or no-ops if already manual) instead of duplicating a heavy content row. - Client: mod+S hotkey + menu button (hidden when readOnly), history-panel kind badges, dimmed autosaves, a 'versions only' filter (indices map to the full list so diff/restore still target the true previous snapshot), live refresh on version.saved. Internal review: APPROVE-with-suggestions; the round-1 WARNING (idle starvation) is fixed here via the max-wait ceiling, and the generalized-boundary + ceiling behaviours are pinned with new tests (115 collab/repo specs green, server tsc 0). Deferred to later PRs: shares.published_mode (PR-2), the save_page_version MCP tool + role prompts (PR-3), actor='git' wiring into #359 (PR-4). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 lines
1.9 KiB
TypeScript
43 lines
1.9 KiB
TypeScript
import { atom } from "jotai";
|
|
// Type-only: these atoms only hold an Editor reference for typing. A value
|
|
// import would drag the whole @tiptap/core engine into the eager graph of every
|
|
// shell component that reads one of these atoms.
|
|
import type { Editor } from "@tiptap/core";
|
|
import type { HocuspocusProvider } from "@hocuspocus/provider";
|
|
import { PageEditMode } from "@/features/user/types/user.types.ts";
|
|
import type { DictationUnavailableReason } from "@/features/dictation/dictation-status";
|
|
|
|
export const pageEditorAtom = atom<Editor | null>(null);
|
|
|
|
// #370 — the active page's collab provider, published by the page editor so the
|
|
// header menu can emit the "save-version" stateless signal (Cmd+S / button).
|
|
// Null when the page is read-only / collab isn't connected. A typed initial
|
|
// value (rather than an explicit generic) keeps jotai's overload resolution on
|
|
// the writable PrimitiveAtom branch.
|
|
const initialCollabProvider: HocuspocusProvider | null = null;
|
|
export const collabProviderAtom = atom(initialCollabProvider);
|
|
|
|
export const titleEditorAtom = atom<Editor | null>(null);
|
|
|
|
export const readOnlyEditorAtom = atom<Editor | null>(null);
|
|
|
|
export const yjsConnectionStatusAtom = atom<string>("");
|
|
|
|
export const showLinkMenuAtom = atom(false);
|
|
|
|
// Current page's edit mode — initialized from the user's saved preference on
|
|
// first load, can be toggled locally without persisting to the server.
|
|
export const currentPageEditModeAtom = atom<PageEditMode>(PageEditMode.Edit);
|
|
|
|
// Whether the dictation mic can start, and (when it can't) the cause-specific
|
|
// reason the mic button surfaces as a tooltip. Published by the page editor,
|
|
// consumed by DictationGroup -> MicButton.
|
|
export type DictationAvailability = {
|
|
isEditable: boolean;
|
|
reason: DictationUnavailableReason | null;
|
|
};
|
|
export const dictationAvailabilityAtom = atom<DictationAvailability>({
|
|
isEditable: false,
|
|
reason: null,
|
|
});
|