Skip to content

Conversation

@sid597
Copy link
Collaborator

@sid597 sid597 commented Dec 5, 2025

Solution

Implemented one-time migration that extracts starred pages from Roam's native .starred-pages DOM and adds them to Left Sidebar $\to$ Global-Section $\to$ Children in the graph configuration.

Cases Handled

  1. Fresh Install (New Graph)

    • Condition: No "Favorites Migrated" flag, no Personal-Section exists
    • Action: Extract starred pages $\to$ Add to Global-Section $\to$ Set flag
    • Result: User's favorites preserved in new sidebar
  2. Existing Install (Collaborative Graph)

    • Condition: Any user has {userId}/Personal-Section under Left Sidebar
    • Action: Skip extraction, set "Favorites Migrated" flag
    • Result: No migration runs, existing configuration untouched
  3. Fresh Install, Multiple Sessions

    • Condition: Migration ran in previous session, graph reopened
    • Action: Check "Favorites Migrated" flag $\to$ early return
    • Result: No duplicate migration
  4. Multi-User Graph (Late Joiner)

    • Condition: User B joins after User A already configured plugin
    • Action: Detects User A's Personal-Section exists $\to$ skip migration
    • Result: No interference with existing setup

Key Design Decisions

  • Graph-level flag: "Favorites Migrated" stored under Left Sidebar (not per-user) ensures migration happens once per graph
  • Personal-Section heuristic: Check for */Personal-Section pattern to detect if plugin already in use by any user
  • Cache refresh: Call refreshConfigTree() after creating flag to prevent duplicate blocks in same session
  • Performance: Flag check is $O(1)$, expensive tree traversal only runs once per graph lifetime

Technical Notes

  • Migration runs in mountLeftSidebar before DOM is cleared
  • Uses getUidAndBooleanSetting pattern consistent with existing config flags
  • Async operation with proper await chains to ensure flag is created before subsequent calls

https://www.loom.com/share/fc639adb1f4341d68403a19094b6dd8c

Summary by CodeRabbit

  • New Features

    • Starred pages now automatically migrate into the Left Sidebar with duplicate title deduplication.
    • Added a migration status setting so the app tracks when migration has completed.
  • Bug Fixes / Reliability

    • Sidebar mounting now runs migration reliably during startup so migrated favorites appear consistently without user action.

✏️ Tip: You can customize this high-level summary in your review settings.

@linear
Copy link

linear bot commented Dec 5, 2025

@supabase
Copy link

supabase bot commented Dec 5, 2025

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

@sid597 sid597 marked this pull request as ready for review December 5, 2025 06:28
Copy link
Collaborator Author

sid597 commented Dec 5, 2025

@sid597
Copy link
Collaborator Author

sid597 commented Dec 5, 2025

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

📝 Walkthrough

Walkthrough

Adds a favorites migration that converts existing starred-pages into the Left Sidebar block structure, tracks migration via a new BooleanSetting, and makes mountLeftSidebar asynchronous to invoke and await the migration when needed.

Changes

Cohort / File(s) Summary
Left Sidebar Async Migration
apps/roam/src/components/LeftSidebarView.tsx
Adds an internal migrateFavorites helper to move starred pages into Left Sidebar blocks (creates/mutates Left Sidebar, Global-Section, Children blocks; deduplicates titles; marks migration complete). Converts mountLeftSidebar to async and awaits migration when a starred-pages container exists. Adds imports: BooleanSetting, getBasicTreeByParentUid, DISCOURSE_CONFIG_PAGE_TITLE, and uses getPageUidByPageTitle.
Settings Flag for Migration
apps/roam/src/utils/getLeftSidebarSettings.ts
Adds favoritesMigrated: BooleanSetting to LeftSidebarConfig and returns it from getLeftSidebarSettings via getUidAndBooleanSetting.
Observer invocation change
apps/roam/src/utils/initializeObserversAndListeners.ts
Calls mountLeftSidebar(...) with a void expression (explicitly ignoring the returned Promise) instead of a bare invocation.

Sequence Diagram(s)

sequenceDiagram
    participant LeftSidebar as LeftSidebarView
    participant Starred as StarredPagesContainer
    participant Config as DiscourseConfigPage
    participant RoamAPI as Roam API

    Note over LeftSidebar,Starred: Mount flow on extension load
    LeftSidebar->>Starred: Detect existing starred-pages container
    LeftSidebar->>LeftSidebar: call migrateFavorites()
    LeftSidebar->>RoamAPI: get pages from starred container
    RoamAPI-->>LeftSidebar: list of starred page titles/uids
    LeftSidebar->>Config: get Left Sidebar config & favoritesMigrated flag
    Config-->>LeftSidebar: config uid and favoritesMigrated setting
    alt not migrated
        LeftSidebar->>RoamAPI: get/create Left Sidebar, Global-Section, Children blocks
        RoamAPI-->>LeftSidebar: created/located block uids
        LeftSidebar->>RoamAPI: create/update child blocks for titles (deduplicate)
        RoamAPI-->>LeftSidebar: created child block uids
        LeftSidebar->>Config: set favoritesMigrated = true
        Config-->>LeftSidebar: acknowledge
    else already migrated
        LeftSidebar-->>LeftSidebar: skip migration
    end
    LeftSidebar->>Starred: clear wrappers / cleanup DOM
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Migration logic (tree traversal, block creation, deduplication) in LeftSidebarView.tsx needs careful review.
  • Verify favoritesMigrated setting read/write sequencing and correctness in getLeftSidebarSettings.ts.
  • Confirm void mountLeftSidebar(...) usage in initializeObserversAndListeners.ts is intentional and that callers don't need to await.

Possibly related PRs

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title refers to 'moving shortcuts to global shortcuts', but the actual changes implement a one-time migration of starred pages to the Left Sidebar's Global-Section. The title is misleading—'shortcuts' is not the correct terminology for what's being migrated (starred pages), and 'move existing shortcuts to new global shortcuts' doesn't accurately describe the migration logic. Revise the title to accurately reflect the main change, such as: 'ENG-1069: Migrate starred pages to Left Sidebar global section' or 'ENG-1069: Add one-time favorites migration to Left Sidebar'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/roam/src/components/LeftSidebarView.tsx (1)

412-499: migrateFavorites logic matches the one-time migration design

The helper cleanly implements the intended flow:

  • Respects the graph-level favoritesMigrated flag and exits early when set.
  • Uses the */Personal-Section heuristic against the live Left Sidebar tree to avoid touching graphs where the plugin has already been configured by any user.
  • Lazily creates Left Sidebar, Global-Section, and Children blocks when missing, using getBasicTreeByParentUid to reuse existing ones when present.
  • Reads starred page titles from the legacy .starred-pages DOM, dedupes against existing Children entries, and only writes missing ones.
  • Marks migration complete by creating a Favorites Migrated child under Left Sidebar and refreshing the config tree, so subsequent sessions are fast.

Two small, optional refinements you might consider:

  • Add an explicit return type for clarity and to align with your TS guidelines:

-const migrateFavorites = async (starredPagesContainer: Element) => {
+const migrateFavorites = async (

  • starredPagesContainer: Element,
    +): Promise => {
    // ...
    }

- If you ever expect duplicate DOM entries in `.starred-pages`, you could dedupe `titles` before filtering against `existingTitles` (right now dedupe only happens vs. existing children, not within the new list itself).

Neither is blocking; the current implementation is sound and matches the PR objectives.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 79af2b74aff7c88464e49059b726914c2a4694fe and f4c95d50fa8e0248f2542bfc2e1480ea7358717d.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `apps/roam/src/components/LeftSidebarView.tsx` (2 hunks)
* `apps/roam/src/utils/getLeftSidebarSettings.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (5)</summary>

<details>
<summary>**/*.{ts,tsx}</summary>


**📄 CodeRabbit inference engine (.cursor/rules/main.mdc)**

> `**/*.{ts,tsx}`: Use Tailwind CSS for styling where possible
> When refactoring inline styles, use tailwind classes
> Prefer `type` over `interface` in TypeScript
> Use explicit return types for functions
> Avoid `any` types when possible
> Prefer arrow functions over regular function declarations
> Use named parameters (object destructuring) when a function has more than 2 parameters
> Use PascalCase for components and types
> Use camelCase for variables and functions
> Use UPPERCASE for constants
> Function names should describe their purpose clearly
> Prefer early returns over nested conditionals for better readability

Files:
- `apps/roam/src/components/LeftSidebarView.tsx`
- `apps/roam/src/utils/getLeftSidebarSettings.ts`

</details>
<details>
<summary>apps/roam/**/*.{js,ts,tsx,jsx,json}</summary>


**📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)**

> Prefer existing dependencies from package.json when working on the Roam Research extension

Files:
- `apps/roam/src/components/LeftSidebarView.tsx`
- `apps/roam/src/utils/getLeftSidebarSettings.ts`

</details>
<details>
<summary>apps/roam/**/*.{ts,tsx,jsx,js,css,scss}</summary>


**📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)**

> Use BlueprintJS 3 components and Tailwind CSS for platform-native UI in the Roam Research extension

Files:
- `apps/roam/src/components/LeftSidebarView.tsx`
- `apps/roam/src/utils/getLeftSidebarSettings.ts`

</details>
<details>
<summary>apps/roam/**/*.{ts,tsx,js,jsx}</summary>


**📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)**

> `apps/roam/**/*.{ts,tsx,js,jsx}`: Use the roamAlphaApi docs from https://roamresearch.com/#/app/developer-documentation/page/tIaOPdXCj when implementing Roam functionality
> Use Roam Depot/Extension API docs from https://roamresearch.com/#/app/developer-documentation/page/y31lhjIqU when implementing extension functionality

Files:
- `apps/roam/src/components/LeftSidebarView.tsx`
- `apps/roam/src/utils/getLeftSidebarSettings.ts`

</details>
<details>
<summary>apps/roam/**</summary>


**📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)**

> Implement the Discourse Graph protocol in the Roam Research extension

Files:
- `apps/roam/src/components/LeftSidebarView.tsx`
- `apps/roam/src/utils/getLeftSidebarSettings.ts`

</details>

</details><details>
<summary>🧬 Code graph analysis (2)</summary>

<details>
<summary>apps/roam/src/components/LeftSidebarView.tsx (2)</summary><blockquote>

<details>
<summary>apps/roam/src/utils/renderNodeConfigPage.ts (1)</summary>

* `DISCOURSE_CONFIG_PAGE_TITLE` (29-29)

</details>
<details>
<summary>apps/roam/src/utils/discourseConfigRef.ts (1)</summary>

* `getFormattedConfigTree` (51-86)

</details>

</blockquote></details>
<details>
<summary>apps/roam/src/utils/getLeftSidebarSettings.ts (1)</summary><blockquote>

<details>
<summary>apps/roam/src/utils/getExportSettings.ts (2)</summary>

* `BooleanSetting` (12-12)
* `getUidAndBooleanSetting` (56-62)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>apps/roam/src/utils/getLeftSidebarSettings.ts (2)</summary><blockquote>

`37-45`: **favoritesMigrated added to LeftSidebarConfig is well-scoped**

Adding `favoritesMigrated: BooleanSetting` to `LeftSidebarConfig` cleanly exposes the graph-level migration flag and matches how it’s consumed in `LeftSidebarView` (`config.favoritesMigrated.value`). Type choice and placement look correct.

---

`174-181`: **favoritesMigrated lookup correctly uses Left Sidebar children**

Using `getUidAndBooleanSetting` against `leftSidebarChildren` with text `"Favorites Migrated"` aligns with how the flag is written in `migrateFavorites` (as a direct child of `Left Sidebar`). Missing-flag behavior (`value === false`) is safe and keeps the migration idempotent.

</blockquote></details>
<details>
<summary>apps/roam/src/components/LeftSidebarView.tsx (1)</summary><blockquote>

`42-43`: **New imports line up with migration responsibilities**

`getBasicTreeByParentUid` and `DISCOURSE_CONFIG_PAGE_TITLE` are the right primitives for reading/patching the config tree during migration; both are used only in `migrateFavorites`, keeping concerns localized.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@sid597 sid597 changed the title Move existing shortcuts to new global shortcuts ENG-1069: Move existing shortcuts to new global shortcuts Dec 5, 2025
@sid597 sid597 force-pushed the eng-1069-check-if-the-existing-shortcuts-move-to-the-global-shortcuts branch from 9df36dd to 35e8404 Compare December 7, 2025 08:25
@sid597 sid597 requested a review from mdroidian December 7, 2025 08:25
@sid597 sid597 force-pushed the eng-1069-check-if-the-existing-shortcuts-move-to-the-global-shortcuts branch from 35e8404 to 459bd2c Compare December 7, 2025 08:53
@mdroidian
Copy link
Contributor

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 7, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/roam/src/components/LeftSidebarView.tsx (2)

419-419: Remove redundant type assertion.

The favoritesMigrated field is already typed as BooleanSetting in the LeftSidebarConfig interface (Line 39 in getLeftSidebarSettings.ts), so the type assertion is unnecessary.

-  if ((config.favoritesMigrated as BooleanSetting).value) return;
+  if (config.favoritesMigrated.value) return;

489-502: Consider consolidating config refresh calls.

If newTitles.length > 0, the code calls refreshAndNotify() (Line 495) and then refreshConfigTree() (Line 502), resulting in two refresh operations. Since refreshAndNotify() already calls refreshConfigTree(), the second call is redundant when titles are added.

Refactor to refresh once:

   const newTitles = titles.filter((t) => !existingTitles.has(t));
 
   if (newTitles.length > 0) {
     await Promise.all(
       newTitles.map((text) =>
         createBlock({ parentUid: childrenUid, node: { text } }),
       ),
     );
-    refreshAndNotify();
   }
 
   await createBlock({
     parentUid: leftSidebarUid,
     node: { text: "Favorites Migrated" },
   });
-  refreshConfigTree();
+  refreshAndNotify();

This calls refreshAndNotify() once at the end, covering both the title additions and the flag creation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 016e942 and 459bd2c.

📒 Files selected for processing (3)
  • apps/roam/src/components/LeftSidebarView.tsx (3 hunks)
  • apps/roam/src/utils/getLeftSidebarSettings.ts (2 hunks)
  • apps/roam/src/utils/initializeObserversAndListeners.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/main.mdc)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Prefer type over interface in TypeScript
Use explicit return types for functions
Avoid any types when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Function names should describe their purpose clearly
Prefer early returns over nested conditionals for better readability

Files:

  • apps/roam/src/utils/initializeObserversAndListeners.ts
  • apps/roam/src/utils/getLeftSidebarSettings.ts
  • apps/roam/src/components/LeftSidebarView.tsx
apps/roam/**/*.{js,ts,tsx,jsx,json}

📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)

Prefer existing dependencies from package.json when working on the Roam Research extension

Files:

  • apps/roam/src/utils/initializeObserversAndListeners.ts
  • apps/roam/src/utils/getLeftSidebarSettings.ts
  • apps/roam/src/components/LeftSidebarView.tsx
apps/roam/**/*.{ts,tsx,jsx,js,css,scss}

📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)

Use BlueprintJS 3 components and Tailwind CSS for platform-native UI in the Roam Research extension

Files:

  • apps/roam/src/utils/initializeObserversAndListeners.ts
  • apps/roam/src/utils/getLeftSidebarSettings.ts
  • apps/roam/src/components/LeftSidebarView.tsx
apps/roam/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)

apps/roam/**/*.{ts,tsx,js,jsx}: Use the roamAlphaApi docs from https://roamresearch.com/#/app/developer-documentation/page/tIaOPdXCj when implementing Roam functionality
Use Roam Depot/Extension API docs from https://roamresearch.com/#/app/developer-documentation/page/y31lhjIqU when implementing extension functionality

Files:

  • apps/roam/src/utils/initializeObserversAndListeners.ts
  • apps/roam/src/utils/getLeftSidebarSettings.ts
  • apps/roam/src/components/LeftSidebarView.tsx
apps/roam/**

📄 CodeRabbit inference engine (.cursor/rules/roam.mdc)

Implement the Discourse Graph protocol in the Roam Research extension

Files:

  • apps/roam/src/utils/initializeObserversAndListeners.ts
  • apps/roam/src/utils/getLeftSidebarSettings.ts
  • apps/roam/src/components/LeftSidebarView.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-06T13:48:35.007Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-11-06T13:48:35.007Z
Learning: In apps/roam/src/utils/createReifiedBlock.ts, the `setBlockProps` function does not need await as it is synchronous or fire-and-forget.

Applied to files:

  • apps/roam/src/components/LeftSidebarView.tsx
📚 Learning: 2025-11-05T21:57:14.909Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 534
File: apps/roam/src/utils/createReifiedBlock.ts:40-48
Timestamp: 2025-11-05T21:57:14.909Z
Learning: In the discourse-graph repository, the function `getPageUidByPageTitle` from `roamjs-components/queries/getPageUidByPageTitle` is a synchronous function that returns a string directly (the page UID or an empty string if not found), not a Promise. It should be called without `await`.

Applied to files:

  • apps/roam/src/components/LeftSidebarView.tsx
🧬 Code graph analysis (3)
apps/roam/src/utils/initializeObserversAndListeners.ts (1)
apps/roam/src/components/LeftSidebarView.tsx (1)
  • mountLeftSidebar (505-525)
apps/roam/src/utils/getLeftSidebarSettings.ts (1)
apps/roam/src/utils/getExportSettings.ts (2)
  • BooleanSetting (12-12)
  • getUidAndBooleanSetting (56-62)
apps/roam/src/components/LeftSidebarView.tsx (3)
apps/roam/src/utils/renderNodeConfigPage.ts (1)
  • DISCOURSE_CONFIG_PAGE_TITLE (29-29)
apps/roam/src/utils/discourseConfigRef.ts (1)
  • getFormattedConfigTree (51-86)
apps/roam/src/utils/getExportSettings.ts (1)
  • BooleanSetting (12-12)
🔇 Additional comments (2)
apps/roam/src/utils/getLeftSidebarSettings.ts (1)

39-39: LGTM! Clean integration of migration flag.

The favoritesMigrated field follows the existing pattern for boolean settings (e.g., collapsable, folded), uses getUidAndBooleanSetting correctly, and aligns with the migration logic in LeftSidebarView.tsx.

Also applies to: 174-180

apps/roam/src/components/LeftSidebarView.tsx (1)

505-524: LGTM! Async mounting with proper migration sequencing.

The signature change to async with explicit Promise<void> return type is correct. Migration runs before DOM manipulation (Line 514 before Line 515), and the guard at Line 513 ensures migration only runs when the root element doesn't exist, preventing redundant execution within the same mount cycle.

Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

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

Nice. Couple suggestions, but generally looks good 👍

if (existingStarred) {
existingStarred.remove();
}
await migrateFavorites();
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally we only run this when the left sidebar flag is turned on. It is probably unncessary to run every load.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah and this will only run when the flag is turned on

Copy link
Contributor

Choose a reason for hiding this comment

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

But this runs on every load, correct? That is not ideal.

@sid597 sid597 force-pushed the eng-1069-check-if-the-existing-shortcuts-move-to-the-global-shortcuts branch from 459bd2c to 4027227 Compare December 11, 2025 17:18
@sid597 sid597 merged commit a6f09ae into main Dec 11, 2025
6 checks passed
@sid597 sid597 deleted the eng-1069-check-if-the-existing-shortcuts-move-to-the-global-shortcuts branch December 11, 2025 17:20
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.

3 participants