[ENG-1856] Store source identity metadata for Roam imported nodes#1161
[ENG-1856] Store source identity metadata for Roam imported nodes#1161sid597 wants to merge 2 commits into
Conversation
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.
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Sibling block-props utils carry no JSDoc; reviewer orientation moves to PR review comments rather than living in the code.
| }, | ||
| }); | ||
|
|
||
| describe("toImportedSourceIdentity", () => { |
There was a problem hiding this comment.
What this covers: the two PURE functions — toImportedSourceIdentity (CrossAppNode → stored shape, using direct content as the title) and parseImportedSourceIdentity (round-trip + reject no-props / no importedFrom / missing field / wrong type / unknown sourceApp).
Run from apps/roam: pnpm test src/utils/__tests__/importedSourceIdentity.test.ts
| sourceTitle: identity.sourceTitle, | ||
| sourceModifiedAt: identity.sourceModifiedAt, | ||
| }; | ||
| setBlockProps(pageUid, { [DISCOURSE_GRAPH_PROP_NAME]: dgData }); |
There was a problem hiding this comment.
Merged into the shared discourse-graph namespace (not replaced) so sibling props like relation-migration survive. Block-props rather than page content = survives a content overwrite on refresh, and is queryable from datalog (see find below).
| setBlockProps(pageUid, { [DISCOURSE_GRAPH_PROP_NAME]: dgData }); | ||
| }; | ||
|
|
||
| export const findImportedNodeUidByRid = async ( |
There was a problem hiding this comment.
Duplicate-prevention entry point: before importing, look up by sourceNodeRid (the canonical cross-app key — it alone identifies a node) and reuse the page if found. Warns instead of throwing on >1 match so a bad data state degrades gracefully.
|
@sid597 could you create a Loom video of you demonstrating the |
|
@mdroidian added the loom video |
| sourceApp: "obsidian", | ||
| sourceSpaceId: "obsidian:9a8b7c6d5e4f3210", | ||
| sourceNodeId: "0192f1a0-7b3c-7e2a-9f10-1a2b3c4d5e6f", | ||
| sourceNodeRid: |
There was a problem hiding this comment.
I'm puzzled that the structure contains both the sourceNodeRid on the one hand, and the set of sourceSpaceId, sourceNodeId, and sourceApp on the other. The former is a compressed representation of all the latter, and we have a utility function to recover them. So this is very redundant. I would only keep the Rid.
If you compare to the obsidian implementation, OTH, we also have authorId (which I understand we may punt; we have not thought enough about multiple authors.)
| @@ -0,0 +1,38 @@ | |||
| import type { CrossAppNode } from "@repo/database/crossAppNodeContract"; | |||
There was a problem hiding this comment.
Nit: Should the example live in the test folder?
maparent
left a comment
There was a problem hiding this comment.
Submitting the review failed, so I have two comments which are somehow not associated with the review.
https://www.loom.com/share/cc332f43f68342efb10b35764bf866b3
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 theCrossAppNodecontract:sourceApp,sourceSpaceId,sourceNodeId,sourceNodeRid,sourceTitle,sourceModifiedAt.toImportedSourceIdentity(node)— derive the stored identity from aCrossAppNode(title from thedirectcontent variant).writeImportedSourceIdentity(uid, identity)/readImportedSourceIdentity(uid)— persist/read under the page's:block/props→discourse-graph→importedFrom, merging alongside any sibling discourse-graph props (mirrorsmigrateRelations/createReifiedBlock).findImportedNodeUidByRid(rid)— datalog lookup returning an already-imported page's uid (duplicate prevention), keyed on the canonicalsourceNodeRid.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:
findImportedNodeUidByRidwriteImportedSourceIdentity(uid, toImportedSourceIdentity(node))sourceModifiedAtDeliberately out of scope (named owners): node-type mapping → ENG-1858; author/provenance → ENG-1875; discovery UI → ENG-1855.
Notes for review
IMPORTED_FROM_PROP_KEYis exported following the existingDISCOURSE_GRAPH_PROP_NAMEprecedent (wire-key constants exported from their owning module); ENG-1861 (refresh-all) / ENG-1875 (provenance) will enumerate imported pages by it.as [string][]on the datalog result matchesstrictQueryForReifiedBlocks's handling of untyped query output.CrossAppNode+rid.ts); branched offmain@36e3348.Tests
toImportedSourceIdentityandparseImportedSourceIdentity(round-trip + rejection of missing / wrong-typed fields and unknownsourceApp, and coexistence with sibling discourse-graph props): 8/8 pass.tscerrors indiscourseNodeSearchProviders.tsare 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 pagePUID: