Skip to content

ENG-1300 Publish relations when a node is published#827

Merged
maparent merged 10 commits intomainfrom
eng-1300-publish-relations-when-a-node-is-published
Feb 28, 2026
Merged

ENG-1300 Publish relations when a node is published#827
maparent merged 10 commits intomainfrom
eng-1300-publish-relations-when-a-node-is-published

Conversation

@maparent
Copy link
Collaborator

@maparent maparent commented Feb 25, 2026

@linear
Copy link

linear bot commented Feb 25, 2026

@supabase
Copy link

supabase bot commented Feb 25, 2026

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

devin-ai-integration[bot]

This comment was marked as resolved.


const skipPublishAccess =
commonGroups.length > 0 && lastModified <= lastModifiedDb;
existingPublish.contains(myGroup) && lastModified <= lastModifiedDb;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScript arrays don't have a .contains() method. This will throw a runtime error when determining whether to skip publish access.

// Current (broken):
existingPublish.contains(myGroup) && lastModified <= lastModifiedDb;

// Fixed:
existingPublish.includes(myGroup) && lastModified <= lastModifiedDb;
Suggested change
existingPublish.contains(myGroup) && lastModified <= lastModifiedDb;
existingPublish.includes(myGroup) && lastModified <= lastModifiedDb;

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

devin-ai-integration[bot]

This comment was marked as resolved.

@maparent maparent force-pushed the eng-1300-publish-relations-when-a-node-is-published branch from 8cf253a to 9119d2f Compare February 25, 2026 17:31
graphite-app[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@maparent maparent force-pushed the eng-1300-publish-relations-when-a-node-is-published branch from 9926af2 to 216f017 Compare February 25, 2026 18:29
const commonGroups = existingPublish.filter((g) => myGroups.has(g));
// temporary single-group assumption
const myGroup = (commonGroups.length > 0 ? commonGroups : [...myGroups])[0]!;
return await publishNodeToGroup({ plugin, file, frontmatter, myGroup });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re publishNodeRelations blocking publishNode: I am not sure it's an error. Basically, if we throw here, the user knows the node is not published, and may try again. Otherwise, the node will be said to be published, and we won't have another opportunity to know to publish the relations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Could fail silently after we create the scan for unpublished relations.)

@maparent maparent force-pushed the eng-1300-publish-relations-when-a-node-is-published branch from e01f542 to 18be22b Compare February 26, 2026 03:47
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

graphite-app[bot]

This comment was marked as resolved.

@maparent maparent force-pushed the eng-1300-publish-relations-when-a-node-is-published branch from a6034d5 to 024a9ee Compare February 26, 2026 14:06
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Collaborator

@trangdoan982 trangdoan982 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except the nit i think this lgtm. This handles the cases where relations are newly created, when a new node is published well.

we should still have a documentation somewhere of all the current cases that we're handling publishing relations right now. maybe we should add it in the official doc or as a README in the publish folder?!

graphite-app[bot]

This comment was marked as resolved.

@maparent maparent merged commit ce51bc3 into main Feb 28, 2026
9 checks passed
@maparent maparent deleted the eng-1300-publish-relations-when-a-node-is-published branch February 28, 2026 00:02
@maparent
Copy link
Collaborator Author

Re the documentation: I think it belongs in the main documentation.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 25 additional findings in Devin Review.

Open in Devin Review

Comment on lines +120 to +125
try {
const published = await publishNewRelation(plugin, instance);
if (published) {
// save again with publication data
await saveRelations(plugin, data);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Stale data object saved after long-running publishNewRelation, overwriting concurrent relation changes

In addRelationNoCheck, the data object is loaded from disk at line 116. After saving the new relation at line 119, the long-running publishNewRelation is called at line 121, which internally runs a full syncAllNodesAndRelations involving multiple network requests (can take seconds). After it returns, data is saved again at line 124 to persist the publishedToGroupId update.

Root Cause and Impact

The data object at line 124 was loaded at line 116, before the sync started. If any other async operation modifies the relations file during the sync window (e.g., another addRelation, removeRelationById, or publishNodeRelations from publishNode.ts:214-230), save #2 at line 124 overwrites those changes with the stale data snapshot — losing any concurrently added/removed relations.

Before this PR, addRelationNoCheck had a trivial load→modify→save window (microseconds). Now the window spans the entire publishNewRelation call including syncAllNodesAndRelations(plugin, context, true) at apps/obsidian/src/utils/publishNode.ts:124, which collects all vault nodes, queries Supabase timestamps, filters relation concepts, and performs an RPC upsert — significantly widening the race window.

The fix is to re-load fresh data before save #2 instead of reusing the stale data:

if (published) {
    const freshData = await loadRelations(plugin);
    const rel = freshData.relations[id];
    if (rel) {
        rel.publishedToGroupId = instance.publishedToGroupId;
        await saveRelations(plugin, freshData);
    }
}

Impact: Any relation created, deleted, or modified by a concurrent async operation during the publishNewRelation window will be silently lost when the stale data is written back to disk.

Suggested change
try {
const published = await publishNewRelation(plugin, instance);
if (published) {
// save again with publication data
await saveRelations(plugin, data);
}
try {
const published = await publishNewRelation(plugin, instance);
if (published) {
// re-load fresh data to avoid overwriting concurrent changes
const freshData = await loadRelations(plugin);
const rel = freshData.relations[id];
if (rel) {
rel.publishedToGroupId = instance.publishedToGroupId;
await saveRelations(plugin, freshData);
}
}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants