Skip to content

[ENG-1856] Store source identity metadata for Roam imported nodes#1161

Open
sid597 wants to merge 1 commit into
mainfrom
eng-1856-store-source-identity-metadata-for-roam-imported-nodes
Open

[ENG-1856] Store source identity metadata for Roam imported nodes#1161
sid597 wants to merge 1 commit into
mainfrom
eng-1856-store-source-identity-metadata-for-roam-imported-nodes

Conversation

@sid597

@sid597 sid597 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

What

Adds the Roam-side storage layer for imported cross-app node identity (ENG-1856, M2 "Bidirectional node import"). When Roam imports an Obsidian-origin shared node, it must remember the node's stable source identity so it can (a) prevent duplicate imports and (b) refresh it later.

apps/roam/src/utils/importedSourceIdentity.ts:

  • ImportedSourceIdentity — 6 app-neutral fields mirroring the CrossAppNode contract: sourceApp, sourceSpaceId, sourceNodeId, sourceNodeRid, sourceTitle, sourceModifiedAt.
  • toImportedSourceIdentity(node) — derive the stored identity from a CrossAppNode (title from the direct content variant).
  • writeImportedSourceIdentity(uid, identity) / readImportedSourceIdentity(uid) — persist/read under the page's :block/propsdiscourse-graphimportedFrom, merging alongside any sibling discourse-graph props (mirrors migrateRelations / createReifiedBlock).
  • findImportedNodeUidByRid(rid) — datalog lookup returning an already-imported page's uid (duplicate prevention), keyed on the canonical sourceNodeRid.

Storage is block-props (not page content), so identity survives content overwrite on import/refresh and is datalog-queryable.

Scope / boundaries (pre-flight approved)

This is a foundational helper PR — the exported helpers are the deliverable; their consumers are downstream M2 issues, so there is no in-PR caller by design:

  • ENG-1855 marks already-imported nodes via findImportedNodeUidByRid
  • ENG-1858 materializer writes identity via writeImportedSourceIdentity(uid, toImportedSourceIdentity(node))
  • ENG-1859 import action dedups; ENG-1860 / ENG-1861 refresh read identity + sourceModifiedAt

Deliberately out of scope (named owners): node-type mapping → ENG-1858; author/provenance → ENG-1875; discovery UI → ENG-1855.

Notes for review

  • IMPORTED_FROM_PROP_KEY is exported following the existing DISCOURSE_GRAPH_PROP_NAME precedent (wire-key constants exported from their owning module); ENG-1861 (refresh-all) / ENG-1875 (provenance) will enumerate imported pages by it.
  • The single as [string][] on the datalog result matches strictQueryForReifiedBlocks's handling of untyped query output.
  • Builds on merged ENG-1847 (CrossAppNode + rid.ts); branched off main@36e3348.

Tests

  • vitest unit tests for the pure functions — toImportedSourceIdentity and parseImportedSourceIdentity (round-trip + rejection of missing / wrong-typed fields and unknown sourceApp, and coexistence with sibling discourse-graph props): 8/8 pass.
  • prettier / eslint / tsc clean on changed files. (Two pre-existing tsc errors in discourseNodeSearchProviders.ts are unrelated to this PR.)

Runtime proof (Loom — owed)

write/read/find call window.roamAlphaAPI; with no UI consumer yet, proof is a console round-trip on a test page PUID:

const id = {sourceApp:"obsidian",sourceSpaceId:"obsidian:vault",sourceNodeId:"abc",sourceNodeRid:"orn:obsidian.note:vault/abc",sourceTitle:"T",sourceModifiedAt:"2026-06-14T10:30:00.000Z"};
await roamAlphaAPI.data.block.update({block:{uid:PUID,props:{"discourse-graph":{importedFrom:id}}}});   // write
roamAlphaAPI.pull("[:block/props]",[":block/uid",PUID]);                                                 // confirm stored
// overwrite the page body here, then re-pull to show identity persists
await roamAlphaAPI.data.async.q(`[:find ?u :in $ ?rid :where [?p :block/uid ?u][?p :block/props ?pr][(get ?pr :discourse-graph) ?d][(get ?d :importedFrom) ?i][(get ?i :sourceNodeRid) ?rid]]`,"orn:obsidian.note:vault/abc"); // find → [[PUID]]; unknown rid → []

Open in Devin Review

Persist an imported cross-app node's stable source identity in the Roam page's block-props under `discourse-graph.importedFrom`, with helpers to derive it from a CrossAppNode, read/write it, and find an imported page by sourceNodeRid for duplicate prevention. Stored in block-props so it survives content overwrite and is datalog-queryable; mirrors the reified-block props pattern and reuses the shared rid.ts helpers. Includes a typed example fixture and unit tests for the pure functions.
@linear-code

linear-code Bot commented Jun 26, 2026

Copy link
Copy Markdown

ENG-1856

@supabase

supabase Bot commented Jun 26, 2026

Copy link
Copy Markdown

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 ↗︎.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
discourse-graph Skipped Skipped Jun 26, 2026 3:27pm

Request Review

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

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.

1 participant