Skip to content

Conversation

@trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Nov 18, 2025

https://www.loom.com/share/0c61409741e94d818c4ad3c87aa6f1e8

Summary by CodeRabbit

Release Notes

  • New Features
    • Configurable hotkey to open node tag suggestions while editing.
    • Interactive node tag suggestion panel with keyboard and mouse navigation to insert tags into your notes.

@linear
Copy link

linear bot commented Nov 18, 2025

@supabase
Copy link

supabase bot commented Nov 18, 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 ↗︎.

@trangdoan982
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

📝 Walkthrough

Walkthrough

This PR adds a hotkey-triggered node tag suggestion feature to the Obsidian plugin. It introduces a configurable hotkey setting in the UI, extends the Settings schema, and registers a CodeMirror editor hotkey handler that opens a new NodeTagSuggestPopover component to suggest and insert node tags.

Changes

Cohort / File(s) Summary
Settings schema extension
apps/obsidian/src/types.ts, apps/obsidian/src/constants.ts
Added nodeTagHotkey: string field to the Settings type with default value \\ in DEFAULT_SETTINGS
UI settings component
apps/obsidian/src/components/GeneralSettings.tsx
Added new "Node tag hotkey" setting item with input control that enforces single-character length and integrates into save behavior
Node tag suggestion popover
apps/obsidian/src/components/NodeTagSuggestModal.tsx
Introduced new exported NodeTagSuggestPopover class that manages a popover UI for node-tag suggestions; handles rendering, keyboard/mouse navigation, selection, and lifecycle management
Plugin hotkey registration
apps/obsidian/src/index.ts
Added setupNodeTagHotkey() private method to register a CodeMirror EditorView keydown handler that opens NodeTagSuggestPopover when configured hotkey is pressed
Dependencies
apps/obsidian/package.json
Added @codemirror/state and @codemirror/view to dev dependencies; added date-fns to runtime dependencies

Sequence Diagram

sequenceDiagram
    actor User
    participant MarkdownView as MarkdownView<br/>(Editor)
    participant CodeMirror as CodeMirror<br/>keydown handler
    participant Plugin as DiscourseGraphPlugin
    participant Popover as NodeTagSuggestPopover
    participant DOM

    User->>MarkdownView: Presses configured hotkey
    MarkdownView->>CodeMirror: Triggers keydown event
    CodeMirror->>Plugin: setupNodeTagHotkey handler
    Plugin->>Popover: open(editor, nodeTypes)
    Popover->>Popover: Filter nodeTypes<br/>and create items
    Popover->>Popover: Compute cursor position
    Popover->>DOM: Create & append popover element
    DOM-->>User: Display tag suggestions
    
    User->>Popover: Navigate & select tag
    alt Keyboard/Mouse selection
        Popover->>MarkdownView: Insert selected tag at cursor
        MarkdownView-->>User: Tag inserted
    end
    
    Popover->>Popover: close()
    Popover->>DOM: Remove popover & event listeners
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Extra attention areas:
    • NodeTagSuggestModal.tsx: Complex event handling, popover lifecycle management, and viewport-aware positioning logic; verify proper event listener cleanup to prevent memory leaks
    • index.ts: Ensure the CodeMirror hotkey handler doesn't interfere with Obsidian's native hotkey system or other handlers
    • Test edge cases in popover (empty node types, cursor at document boundary, rapid hotkey presses)

Possibly related PRs

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[ENG-1052] Enable node tags via command key' clearly summarizes the main change: enabling node tag functionality through a keyboard hotkey/command key.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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 (6)
apps/obsidian/src/components/GeneralSettings.tsx (1)

157-159: Hotkey state and save behavior are sound; confirm semantics for empty value

The single-character guard in handleNodeTagHotkeyChange plus assigning plugin.settings.nodeTagHotkey = nodeTagHotkey || "\\" on save is internally consistent and keeps the hotkey always defined. One implication is that clearing the field with Backspace will be saved as the default "\\", not “no hotkey”, so there’s currently no way to disable the shortcut via this UI. If that’s intentional (“empty means use default”), you’re good; if you ever want a “no hotkey” option, you’d need an explicit toggle or a distinct sentinel value.

Also applies to: 185-191, 198-198

apps/obsidian/src/index.ts (2)

1-1: File-wide eslint disable is broader than necessary

Disabling @typescript-eslint/no-unsafe-assignment for the whole file can hide unrelated unsafe assignments over time. If feasible, consider constraining this to the specific lines that actually need it (e.g., around loadData()), so the rest of the file stays fully checked.


192-225: Hotkey extension setup looks solid; consider an IME-friendly guard

Registering a keydown handler via EditorView.domEventHandlers that reads this.settings.nodeTagHotkey on each event is a good way to keep the shortcut dynamic after settings changes. The preventDefault/stopPropagation + returning true correctly claims the event before opening NodeTagSuggestPopover for the active MarkdownView. As a small robustness tweak, you might optionally early‑return with if (event.isComposing) return false; to avoid interfering with IME composition for non‑Latin input.

apps/obsidian/src/components/NodeTagSuggestModal.tsx (3)

173-181: Consider using event delegation for item interactions.

Individual event listeners are attached to each item element. While these are cleaned up when the popover is removed from the DOM, using event delegation on the container would be more efficient and follows best practices.

Consider attaching a single event listener to the container and using event delegation:

itemsContainer.addEventListener("mousedown", (e) => {
  const target = (e.target as HTMLElement).closest(".node-tag-item");
  if (target) {
    e.preventDefault();
    e.stopPropagation();
    const index = parseInt(target.dataset.index || "0", 10);
    const item = this.items[index];
    if (item) this.selectItem(item);
  }
});

Similarly for mouseenter events.


261-269: Remove redundant closest check.

The check on line 265 is redundant with the contains check on line 264. If this.popover.contains(e.target) is false, the target is already outside the popover.

Apply this diff to simplify:

  this.clickOutsideHandler = (e: MouseEvent) => {
-   if (
-     this.popover &&
-     !this.popover.contains(e.target as Node) &&
-     !(e.target as HTMLElement).closest(".node-tag-suggest-popover")
-   ) {
+   if (this.popover && !this.popover.contains(e.target as Node)) {
      this.close();
    }
  };

76-99: Consider adding accessibility attributes.

The popover lacks ARIA attributes for screen reader support. While keyboard navigation is implemented, adding semantic roles and states would improve accessibility.

Consider adding:

  • role="listbox" on the popover container
  • role="option" on each item element
  • aria-selected="true" on the selected item
  • aria-activedescendant on the popover pointing to the selected item's ID

Example for the popover:

popover.setAttribute("role", "listbox");
popover.setAttribute("aria-label", "Node tag suggestions");

Also applies to: 117-184

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18e7685 and 48fec83.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/obsidian/package.json (2 hunks)
  • apps/obsidian/src/components/GeneralSettings.tsx (3 hunks)
  • apps/obsidian/src/components/NodeTagSuggestModal.tsx (1 hunks)
  • apps/obsidian/src/constants.ts (1 hunks)
  • apps/obsidian/src/index.ts (4 hunks)
  • apps/obsidian/src/types.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-25T15:53:21.799Z
Learnt from: sid597
Repo: DiscourseGraphs/discourse-graph PR: 372
File: apps/roam/src/components/DiscourseNodeMenu.tsx:116-116
Timestamp: 2025-08-25T15:53:21.799Z
Learning: In apps/roam/src/components/DiscourseNodeMenu.tsx, when handling tag insertion, multiple leading hashtags (like ##foo) should be preserved as they represent user intent, not normalized to a single hashtag. The current regex /^#/ is correct as it only removes one leading # before adding one back, maintaining any additional hashtags the user intended.

Applied to files:

  • apps/obsidian/src/index.ts
  • apps/obsidian/src/components/GeneralSettings.tsx
🔇 Additional comments (5)
apps/obsidian/package.json (1)

19-21: New dependencies for editor hotkey support look fine

The added entries align with the new CodeMirror-based hotkey handling and date utilities used by the node-tag popover; nothing concerning here. Please just ensure the workspace lockfile is updated and builds/tests run cleanly with these versions.

Also applies to: 41-41

apps/obsidian/src/types.ts (1)

28-37: Settings wiring for nodeTagHotkey looks consistent

Adding nodeTagHotkey: string cleanly aligns the type with DEFAULT_SETTINGS and GeneralSettings, so settings objects will always carry a defined hotkey.

apps/obsidian/src/constants.ts (1)

47-72: Default nodeTagHotkey value is coherent with settings loading and UI

Using "\\" as the default matches the GeneralSettings placeholder/description and ensures older configs pick up the new field via DEFAULT_SETTINGS + hasNewFields.

apps/obsidian/src/components/GeneralSettings.tsx (1)

277-305: Key capture logic correctly enforces single-character hotkeys

The onKeyDown handler cleanly restricts the setting to printable single characters (plus Backspace to clear) and prevents default insertion when a character hotkey is pressed, which keeps the input and state in sync. Just be aware that modifier combos (e.g. Cmd/Ctrl/Alt + key) will still record the underlying character, not the chord, which seems fine given the “single key” design.

apps/obsidian/src/components/NodeTagSuggestModal.tsx (1)

9-336: Well-structured implementation with clean lifecycle management.

The class demonstrates good separation of concerns with clear method responsibilities, proper event handler lifecycle management (setup/teardown), and appropriate viewport-aware positioning logic. The public API is simple and fits well with the hotkey integration pattern.

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.

Approved to not block, but I strongly recommend we understand and/or fix this issue:

// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access

@@ -0,0 +1,335 @@
import { Editor } from "obsidian";
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we are mostly creating this from scratch? Are there any existing obsidian components we can tie into without having to re-invent the wheel, cover all edge cases, etc? List items, popovers, etc, etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

as far as i know there's no official API to create popovers at tooltip position. The popover API they have are the one that takes over the whole screen

"author": "",
"license": "Apache-2.0",
"devDependencies": {
"@codemirror/state": "^6.5.2",
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this being used?

@trangdoan982 trangdoan982 force-pushed the eng-1052-enable-node-tags-via-hot-key branch from 337a28c to 618f4fa Compare November 28, 2025 22:01
@trangdoan982 trangdoan982 merged commit 32c8cc1 into main Nov 28, 2025
3 of 4 checks passed
@trangdoan982 trangdoan982 deleted the eng-1052-enable-node-tags-via-hot-key branch November 28, 2025 22:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants