openvibers/openvibe.blog Commits

An edit that does not send citations keeps them: the /write editor does not resend a post's citations, so every edit made there dropped them from the new revision. Sending a list (even an empty one) still replaces them. lifecycle test

OP OpenVibers committed 24 Sept 2026 · 0784b429a1 · 2 files, +20 −1


Changes

ddiff --git a/server/domain/posts.js b/server/domain/posts.js
iindex 1707368..6dd6629 100644
---- a/server/domain/posts.js
++++ b/server/domain/posts.js
@@@ -293,7 +293,12 @@ function createPosts({ store, blogs, publication, access, outbox, log = console
});
revision = out.revision;
created = out.created;
-- if (created) attachCitations(post, revision.number, input.citations);
++ if (created) {
++ // An edit that does not send citations keeps the ones it had (the /write editor does not
++ // resend them); sending a list, even an empty one, replaces them.
++ if (input.citations !== undefined) attachCitations(post, revision.number, input.citations);
++ else store.citations.carryForward({ entityId: post.id, fromRevision: head.number, toRevision: revision.number, attachedBy: viewer.subject });
++ }
}
const sets = {};
if (input.slug !== undefined && input.slug !== '' && input.slug !== post.slug) {
ddiff --git a/test/lifecycle.test.js b/test/lifecycle.test.js
iindex 81b8541..0e343eb 100644
---- a/test/lifecycle.test.js
++++ b/test/lifecycle.test.js
@@@ -165,6 +165,20 @@ const LONG = Array.from({ length: 120 }, (_, i) => `word${i}`).join(' ');
assert.strictEqual(r.headers.get('content-type'), 'application/problem+json');
});
++ await check('an edit that does not send citations keeps them; sending a list replaces them', async () => {
++ const made = await t.get('/api/v1/blogs/alice/posts', { as: alice, json: { title: 'Cited', body: `Sources below.\n\n${LONG}`, citations: [{ url: 'https://example.org/a', title: 'A', quote: 'quoted' }] } });
++ assert.strictEqual(made.status, 201, made.text);
++ const id = made.json().post.id;
++ const citesAt = () => { const h = t.ctx.store.revisions.head(id); return t.ctx.store.citations.forRevision(id, h.number).map((c) => c.url); };
++ assert.deepStrictEqual(citesAt(), ['https://example.org/a']);
++ let r = await t.get(`/api/v1/posts/${id}`, { as: alice, method: 'PATCH', json: { expected_revision: t.ctx.store.revisions.head(id).number, body: `Edited in /write.\n\n${LONG}` } });
++ assert.strictEqual(r.status, 200, r.text);
++ assert.deepStrictEqual(citesAt(), ['https://example.org/a'], 'carried forward');
++ r = await t.get(`/api/v1/posts/${id}`, { as: alice, method: 'PATCH', json: { expected_revision: t.ctx.store.revisions.head(id).number, body: `Replaced.\n\n${LONG}`, citations: [] } });
++ assert.strictEqual(r.status, 200, r.text);
++ assert.deepStrictEqual(citesAt(), [], 'an explicit empty list removes them');
++ });
++
await t.close();
done();
})();