refactor(notes): remove legacy whole-document saves; all block changes go through sync
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m14s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m14s
Phase 5 of the collaboration plan:
- Remove PATCH /documents/{id}/blocks, PatchDocumentBlocks, the
DOC_BLOCKS_PATCHED event and its types, FlushDocumentState and
LoadDocumentState. PUT /documents/{id} rejects blocks (400) and only
changes title, icon, folder and pin; editors may now change title/icon.
- The to-do overview ticks via a single operation on /docs/{id}/sync
(new skipLog flag). Task links set the block tick with a targeted
operation and notify open editors; creating a linked task needs edit
rights.
- Keep the three share tables: DocRole already decides centrally, and
rewriting the visibility queries would only add risk (documented in plan).
- Tests adapted to the new paths; share e2e test now also ticks a to-do in
the overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b7a53daab
commit
aea4324c20
18 files changed
+155
-376
No files matched your search
@@ -49,6 +49,7 @@ import {
|
||||
RealtimeTransport,
|
||||
} from '../../lib/api';
|
||||
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
|
||||
import { sendDocOps } from '../../lib/docSync/oneoff';
|
||||
import { useMobileActions } from '../../context/MobileActionContext';
|
||||
import { DocumentEditor, DocSaveStatus } from './DocumentEditor';
|
||||
|
||||
@@ -789,9 +790,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
prev.map((d) => (d.id === doc.id ? { ...d, blocks: updatedBlocks } : d))
|
||||
);
|
||||
try {
|
||||
await api.patchDocumentBlocks(doc.id, {
|
||||
upserted: [{ id: blockId, checked: !currentChecked, type: 'todo', content: '' }],
|
||||
});
|
||||
const [result] = await sendDocOps(doc.id, [{ kind: 'set_props', blockId, patch: { checked: !currentChecked } }]);
|
||||
if (result?.status !== 'applied') loadWorkspaceData();
|
||||
} catch {
|
||||
loadWorkspaceData();
|
||||
}
|
||||
|
||||
+1
-21
@@ -595,11 +595,10 @@ export const api = {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
folderPath?: string;
|
||||
blocks?: DocumentBlock[];
|
||||
tags?: string[];
|
||||
isPinned?: boolean;
|
||||
}
|
||||
): Promise<Document> {
|
||||
// Block content is saved through the note sync engine (lib/docSync), not here
|
||||
const d = await request<Document>(`/api/v1/documents/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(patch),
|
||||
@@ -611,25 +610,6 @@ export const api = {
|
||||
};
|
||||
},
|
||||
|
||||
async patchDocumentBlocks(
|
||||
id: string,
|
||||
payload: {
|
||||
upserted?: DocumentBlock[];
|
||||
deletedIds?: string[];
|
||||
blockOrder?: string[];
|
||||
}
|
||||
): Promise<Document> {
|
||||
const d = await request<Document>(`/api/v1/documents/${id}/blocks`, {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
return {
|
||||
...d,
|
||||
blocks: ensureArray(d.blocks),
|
||||
tags: ensureArray(d.tags),
|
||||
};
|
||||
},
|
||||
|
||||
async joinSSERoom(sseId: string, room: string, action: 'join' | 'leave' = 'join'): Promise<{ success: boolean }> {
|
||||
return request<{ success: boolean }>('/api/v1/events/rooms', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// Sends a few operations for a note that is not open in an editor (e.g. ticking a to-do in the overview).
|
||||
// Goes through the same sync endpoint and rules as the editor, so the change is logged and pushed to
|
||||
// everyone who has the note open.
|
||||
import type { Op, Result } from './model';
|
||||
import { httpTransport } from './transport';
|
||||
|
||||
export async function sendDocOps(docId: string, ops: Omit<Op, 'opId'>[]): Promise<Result[]> {
|
||||
const bytes = new Uint8Array(8);
|
||||
crypto.getRandomValues(bytes);
|
||||
const clientId = `once_${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
|
||||
const res = await httpTransport.sync(docId, {
|
||||
clientId,
|
||||
lastSeq: 0,
|
||||
ops: ops.map((op, i) => ({ ...op, opId: `${clientId}.${i}` }) as Op),
|
||||
skipLog: true,
|
||||
});
|
||||
return res.results;
|
||||
}
|
||||
@@ -37,6 +37,7 @@ export interface SyncRequest {
|
||||
lastSeq: number;
|
||||
ops: Op[];
|
||||
leases?: { hold: string[]; release: string[] };
|
||||
skipLog?: boolean; // one-off changes do not need the log back
|
||||
}
|
||||
|
||||
export interface SyncResponse {
|
||||
|
||||
Reference in new issue
Block a user