feat(notes): keep old shas as immutable snapshots
All checks were successful
CI / verify (push) Successful in 1m5s

Cache content under its own sha (write-once) with the path key as the
latest pointer, and advance the stack handle to the new sha after an
edit or pull. A shared link keeps showing exactly what was shared, while
the live view follows your changes.
This commit is contained in:
Julien Calixte
2026-06-29 00:43:56 +02:00
parent 08d2d804ff
commit 2f8d3c24a4
4 changed files with 99 additions and 2 deletions

View File

@@ -0,0 +1,65 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
vi.mock("@/data/data", () => ({
data: {
get: vi.fn().mockResolvedValue(null),
update: vi.fn().mockResolvedValue(undefined)
},
generateId: (type: string, id: string) => `${type}-${id}`
}))
const addFile = vi.fn()
vi.mock("@/modules/repo/store/userRepo.store", () => ({
useUserRepoStore: () => ({ addFile })
}))
import { data, generateId } from "@/data/data"
import { DataType } from "@/data/DataType.enum"
import { prepareNoteCache } from "./prepareNoteCache"
const writtenIds = () =>
vi.mocked(data.update).mock.calls.map((c) => (c[0] as { _id: string })._id)
describe("prepareNoteCache.saveCacheNote", () => {
beforeEach(() => {
vi.mocked(data.update).mockClear()
addFile.mockClear()
})
it("on edit, keys content by the NEW sha and leaves the viewed sha untouched", async () => {
const { saveCacheNote } = prepareNoteCache("oldSha", "notes/a.md")
await saveCacheNote("new content", {
editedSha: "newSha",
path: "notes/a.md"
})
const ids = writtenIds()
// immutable snapshot under the content's own (new) sha
expect(ids).toContain(generateId(DataType.Note, "newSha"))
// latest pointer under the path
expect(ids).toContain(generateId(DataType.Note, "notes/a.md"))
// the previously-viewed sha stays immutable
expect(ids).not.toContain(generateId(DataType.Note, "oldSha"))
})
it("on a fresh load (no editedSha), keys content by the viewed sha", async () => {
const { saveCacheNote } = prepareNoteCache("sha0", "notes/a.md")
await saveCacheNote("content")
expect(writtenIds()).toContain(generateId(DataType.Note, "sha0"))
})
it("registers the new sha against the path in the store", async () => {
const { saveCacheNote } = prepareNoteCache("oldSha", "notes/a.md")
await saveCacheNote("new content", {
editedSha: "newSha",
path: "notes/a.md"
})
expect(addFile).toHaveBeenCalledWith({ path: "notes/a.md", sha: "newSha" })
})
})

View File

@@ -61,8 +61,14 @@ export const prepareNoteCache = (sha: string, path?: string) => {
content: string,
params?: { editedSha?: string; path?: string }
) => {
// Content is addressed by its OWN sha so snapshots stay immutable: an edit
// writes under the new sha and never overwrites the previously-viewed one.
// The path key (notePath) always holds the latest content (live pointer).
const contentId = params?.editedSha
? generateId(DataType.Note, params.editedSha)
: noteId
const newNote: Note = {
_id: noteId,
_id: contentId,
$type: DataType.Note,
content,
editedSha: params?.editedSha