Skip to content

Conversation

duckduckhero
Copy link
Collaborator

DON'T MERGE THIS YET

Copy link

coderabbitai bot commented Sep 26, 2025

📝 Walkthrough

Walkthrough

Adds in-editor search-and-replace UIs (floating and full-width), tabbed note header/sub-headers and TranscriptViewer, right-panel chat/model UI with dynamic quick actions, TipTap extensions (Document, SearchAndReplace) and styles, UI color/tiny layout tweaks, and session activeTab state.

Changes

Cohort / File(s) Summary
Editor Search & TipTap extensions
apps/desktop/src/components/editor-area/floating-search-box.tsx, apps/desktop/src/components/editor-area/local-search-bar.tsx, packages/tiptap/src/shared/extensions.ts, packages/tiptap/src/editor/index.tsx, apps/desktop/src/styles/globals.css
New FloatingSearchBox and LocalSearchBar components wired to editor commands/storage; registers SearchAndReplace (+ classes) and Document extension; adds CSS .search-result / .search-result-current highlights.
Editor Area core & tabbing
apps/desktop/src/components/editor-area/index.tsx, packages/utils/src/stores/session.ts
Refactors editor area to support tabs (raw/enhanced/transcript); adds session.activeTab and setActiveTab (syncs showRaw); integrates transcript/editor refs and search trigger (Ctrl/Cmd+F).
Note Header: tabs & sub-headers
apps/desktop/src/components/editor-area/note-header/tab-header.tsx, apps/desktop/src/components/editor-area/note-header/tab-sub-header.tsx, apps/desktop/src/components/editor-area/note-header/index.tsx, apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx, apps/desktop/src/components/editor-area/note-header/sub-headers/transcript-sub-header.tsx
Adds TabHeader (forwardRef) and TabSubHeader; exports TabHeader/TabHeaderRef; adds EnhancedNoteSubHeader (template-driven regenerate/cancel) and TranscriptSubHeader (audio existence/open controls).
Transcript viewer/editor
apps/desktop/src/components/editor-area/transcript-viewer.tsx, packages/tiptap/src/transcript/index.tsx, packages/tiptap/src/styles/transcript.css
New TranscriptViewer (live vs finished modes, editor ref propagation, speaker selection); adjusts TranscriptEditor padding/layout and transcript CSS wrapping/overflow.
Note header chips & metadata
apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx, .../chips/tag-chip.tsx, .../chips/participants-chip.tsx, .../chips/index.tsx, apps/desktop/src/components/editor-area/metadata-modal.tsx
Unifies icon/text to neutral color; exports ParticipantsChipInner; removes ShareChip from render; adds MetadataModal popover that shows Event/Participants/Tag chips.
Title input
apps/desktop/src/components/editor-area/note-header/title-input.tsx
Adds optional autoFocus prop with delayed focus behavior when editable and not generating.
Right panel: chat/model/quick actions
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx, .../components/chat/empty-chat-state.tsx, .../components/chat-model-info-modal.tsx, apps/desktop/src/components/right-panel/views/chat-view.tsx, apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts, apps/desktop/src/components/right-panel/index.tsx, apps/desktop/src/contexts/right-panel.tsx
Adds ChatModelInfoModal, model-name badge, queries for LLM connection/model, dynamic quick actions per session (getDynamicQuickActions), passes sessionId into EmptyChatState, reserves space for FloatingActionButtons, defaults right panel view to chat (removes TranscriptView switching).
Toolbar and search bar tweaks
apps/desktop/src/components/toolbar/bars/main-toolbar.tsx, apps/desktop/src/components/search-bar.tsx
Replaces TranscriptPanelButton with ShareButton in toolbar; makes search bar container clickable and inner input non-interactive/disabled.
Minor UI/layout adjustments
apps/desktop/src/components/pro-gate-modal/index.tsx, apps/desktop/src/routes/app.note.$id.tsx, packages/ui/src/components/ui/command.tsx
Increases ProGateModal delay (500→800ms); reduces top padding in note route (pt-6→pt-4); widens CommandDialog container width utility.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant UI as LocalSearch / FloatingSearch
  participant Editor as TipTap Editor
  participant Ext as SearchAndReplace Storage

  User->>UI: Type search term
  UI->>Editor: editor.commands.setSearchTerm(term)
  Editor->>Ext: update matches/storage
  UI->>Ext: read storage.searchAndReplace
  Ext-->>UI: resultCount/currentIndex
  User->>UI: Next/Prev or Enter/F3
  UI->>Editor: editor.commands.goToNext()/goToPrev()
  Editor->>Ext: set current match
  UI->>Editor: scroll current match into view
  User->>UI: Replace / Replace All
  UI->>Editor: editor.commands.replace()/replaceAll()
  Editor->>Ext: apply replacements
  UI->>Ext: read updated resultCount/currentIndex
Loading
sequenceDiagram
  participant Header as TabHeader
  participant Store as Session Store
  participant Area as Editor Area
  participant Viewer as TranscriptViewer
  participant Memo as Memo Editor

  Header->>Store: setActiveTab(tab)
  Store-->>Area: activeTab changed
  alt tab == "transcript"
    Area->>Viewer: render TranscriptViewer(sessionId)
  else tab == "raw"
    Area->>Memo: render raw memo editor
  else tab == "enhanced"
    Area->>Memo: render enhanced memo editor
  end
Loading
sequenceDiagram
  actor User
  participant Chat as ChatInput
  participant Modal as ChatModelInfoModal
  participant Conn as connectorCommands
  participant Nav as windowsCommands/router

  Chat->>Conn: getLlmConnection/getCustomLlmModel/getHyprcloudEnabled
  Conn-->>Chat: connection/model flags
  User->>Chat: Click model badge
  Chat->>Modal: open modal
  User->>Modal: Click "Choose"
  Modal->>Nav: showSettingsWindow() + navigate to /app/settings?tab=llms
  Modal->>Modal: close
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Overall redesign add empty chat” is overly broad and does not clearly summarize the primary changes in the pull request, which include extensive editor-area enhancements (floating search, local search bar, metadata modal, transcript viewer), note-header updates, right-panel chat model info, dynamic quick actions, and styling adjustments beyond just an empty chat UI. It fails to concisely convey the scope or the most impactful changes in a way that teammates can quickly understand. Please revise the title to succinctly reflect the main areas of work—for example, “Enhance editor-area with search UI, metadata modal, transcript viewer, and add empty-chat enhancements”—so that it clearly communicates the scope of changes.
Description Check ⚠️ Warning The description “DON’T MERGE THIS YET” does not describe any of the actual changes in the pull request and is unrelated to the code updates, making it impossible for reviewers to understand the intent or contents of the changeset. Please replace the placeholder note with a summary that outlines the key changes, objectives, and any important context or migration steps to guide reviewers through this large redesign.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch overall-redesign-add-empty-chat

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

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
apps/desktop/src/components/search-bar.tsx (1)

113-147: Disabling the input breaks keyboard focus & history UX

Turning the input into a disabled, pointer-events-none element means it can no longer receive focus or key events. That prevents onFocus, onBlur, and onKeyDown handlers from ever firing, so keyboard navigation (navigateResults, selectResult, clearSearch) and the recent-history dropdown are now dead code. focusSearch (still exposed by useHyprSearch) will also fail because browsers refuse to focus disabled controls. Net effect: keyboard users lose the search entry point entirely. Please keep the element focusable (e.g. remove disabled/pointer-events-none, or replace it with a real button that forwards focus/keyboard events into the palette).

apps/desktop/src/components/editor-area/note-header/title-input.tsx (1)

24-29: Don’t trap Shift+Tab; only intercept Tab forward.

Preventing default on any Tab breaks reverse navigation. Limit to Tab without Shift.

-    if (e.key === "Enter" || e.key === "Tab") {
+    if (e.key === "Enter" || (e.key === "Tab" && !e.shiftKey)) {
apps/desktop/src/components/right-panel/views/chat-view.tsx (2)

34-35: Remove unused state setter.

_setChatHistory is unused. Destructure only the value to satisfy no-unused-vars.

-  const [chatHistory, _setChatHistory] = useState<ChatSession[]>([]);
+  const [chatHistory] = useState<ChatSession[]>([]);

As per coding guidelines


245-247: Selecting a chat from history doesn’t load it.

handleSelectChat ignores the selected id, so history selection won’t switch conversations.

-  const handleSelectChat = (_chatId: string) => {
-    setShowHistory(false);
-  };
+  const handleSelectChat = (chatId: string) => {
+    setCurrentConversationId(chatId);
+    setShowHistory(false);
+  };
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (2)

346-353: Prevent double-submit on Enter (duplicated handling).

Enter is handled both via TipTap handleKeyDown and a DOM keydown listener, causing potential double submission.

Apply this diff to remove the duplicate Enter handling in the DOM listener:

-        if (event.key === "Enter" && !event.shiftKey) {
-          event.preventDefault();
-
-          if (inputValue.trim()) {
-            handleSubmit();
-          }
-        }

260-289: Escape inserted HTML to avoid injection via titles/labels.

selectionRef and data-label embed unescaped note/title text. Escape special chars before composing HTML.

Apply this diff:

-        const selectedHtml = pendingSelection.text || "";
+        const selectedHtml = pendingSelection.text || "";
@@
-        const selectedText = stripHtml(selectedHtml).trim();
+        const selectedText = stripHtml(selectedHtml).trim();
+        const escapeHtml = (s: string) =>
+          s
+            .replace(/&/g, "&amp;")
+            .replace(/</g, "&lt;")
+            .replace(/>/g, "&gt;")
+            .replace(/"/g, "&quot;")
+            .replace(/'/g, "&#39;");
+        const safeNoteName = escapeHtml(noteName);
+        const safeSelectedText = escapeHtml(selectedText);
@@
-        const textPreview = selectedText.length > 0
+        const textPreview = safeSelectedText.length > 0
           ? (selectedText.length > 6
-            ? `'${selectedText.slice(0, 6)}...'` // Use single quotes instead!
-            : `'${selectedText}'`)
+            ? `'${safeSelectedText.slice(0, 6)}...'` // Why: prevent HTML injection
+            : `'${safeSelectedText}'`)
           : "NO_TEXT";
@@
-        const selectionRef = textPreview !== "NO_TEXT"
-          ? `[${noteName} - ${textPreview}(${pendingSelection.startOffset}:${pendingSelection.endOffset})]`
-          : `[${noteName} - ${pendingSelection.startOffset}:${pendingSelection.endOffset}]`;
+        const selectionRef = textPreview !== "NO_TEXT"
+          ? `[${safeNoteName} - ${textPreview}(${pendingSelection.startOffset}:${pendingSelection.endOffset})]`
+          : `[${safeNoteName} - ${pendingSelection.startOffset}:${pendingSelection.endOffset}]`;
@@
-        const escapedSelectionRef = selectionRef.replace(/"/g, "&quot;");
+        const escapedSelectionRef = selectionRef; // already escaped
🧹 Nitpick comments (25)
apps/desktop/src/routes/app.note.$id.tsx (1)

163-167: Reuse computed isMainWindow; avoid duplicate window label calls

You already computed isMainWindow (Line 120). Use it here to prevent redundant calls and keep intent clear.

-            <EditorArea editable={getCurrentWebviewWindowLabel() === "main"} sessionId={sessionId} />
+            <EditorArea editable={isMainWindow} sessionId={sessionId} />
apps/desktop/src/contexts/right-panel.tsx (1)

39-39: Defaulting to “chat” may bypass ChatRightPanel flag gating

Initializing currentView to "chat" shows chat by default even if the ChatRightPanel flag resolves to false later. Confirm this is intended; otherwise, consider syncing the default with the flag.

Minimal follow-up (outside this hunk) to keep transcript as fallback when chat is disabled:

+  useEffect(() => {
+    if (chatPanelEnabled === false && currentView === "chat") {
+      setCurrentView("transcript");
+    }
+  }, [chatPanelEnabled]);
apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (1)

35-43: Use Modal’s built-in overlay instead of a custom one

Simplifies layering and keeps behavior consistent across modals.

-      <div className="fixed inset-0 z-50 bg-black/25 backdrop-blur-sm" onClick={handleClose} />
-
       <Modal
         open={isOpen}
         onClose={handleClose}
         size="md"
-        showOverlay={false}
         className="bg-background w-[480px] max-w-[90vw]"
       >
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)

162-169: Rename ‘ParticipentItem’ typo for clarity

Consistent naming improves readability.

-              <ParticipentItem
+              <ParticipantItem
                 key={member.id}
                 member={member}
                 sessionId={sessionId}
                 handleClickHuman={handleClickHuman}
                 allowRemove={allowMutate}
                 selected={member.id === selectedHuman?.id}
               />
-function ParticipentItem({
+function ParticipantItem({
   member,
   sessionId,
   handleClickHuman,
   allowRemove = true,
   selected = false,
 }: {

Also applies to: 178-191

packages/tiptap/src/styles/transcript.css (1)

25-29: Optional: align speaker-content white-space with container

To avoid inconsistent wrapping between container and speaker lines:

 .transcript-speaker-content {
   display: inline;
   line-height: 1.7;
-  white-space: normal;
+  white-space: pre-wrap;
 }
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (1)

113-119: Use Extended_Pictographic for precise emoji matching
\p{Emoji} is a broad binary property (it includes digits, punctuation, etc.); \p{Extended_Pictographic} more accurately targets actual emoji code points and is supported in all modern Chromium, WebKit, and Gecko engines.

-    const emojiMatch = title.match(/^(\p{Emoji})\s*/u);
+    const emojiMatch = title.match(/^(\p{Extended_Pictographic})\s*/u);
apps/desktop/src/components/editor-area/metadata-modal.tsx (1)

16-20: Add keyboard support for the hover modal trigger.

Enable focus/blur to mirror hover so keyboard users can access the modal.

-    <div
-      className="relative inline-block cursor-pointer"
-      onMouseEnter={() => setIsHovered(true)}
-      onMouseLeave={() => setIsHovered(false)}
-    >
+    <div
+      className="relative inline-block cursor-pointer"
+      onMouseEnter={() => setIsHovered(true)}
+      onMouseLeave={() => setIsHovered(false)}
+      tabIndex={0}
+      onFocus={() => setIsHovered(true)}
+      onBlur={() => setIsHovered(false)}
+    >
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (3)

53-71: Avoid frequent polling; prefer cache semantics.

Refetching all three queries every 5s is costly. Use staleTime and focus-based refetch instead.

Apply this diff:

   const llmConnectionQuery = useQuery({
     queryKey: ["llm-connection"],
     queryFn: () => connectorCommands.getLlmConnection(),
-    refetchOnWindowFocus: true,
-    refetchInterval: 5000,
+    refetchOnWindowFocus: true,
+    staleTime: 30_000,
   });

   const customLlmModelQuery = useQuery({
     queryKey: ["custom-llm-model"],
     queryFn: () => connectorCommands.getCustomLlmModel(),
     enabled: llmConnectionQuery.data?.type === "Custom",
-    refetchInterval: 5000,
+    staleTime: 30_000,
   });

   const hyprCloudEnabledQuery = useQuery({
     queryKey: ["hypr-cloud-enabled"],
     queryFn: () => connectorCommands.getHyprcloudEnabled(),
-    refetchInterval: 5000,
+    staleTime: 30_000,
   });

360-361: Remove ineffective return false from DOM listener.

return false in addEventListener handlers has no effect (unlike jQuery). You already call preventDefault/stopPropagation.

Apply this diff:

-          return false;

21-26: onKeyDown prop is effectively unused.

It’s declared but not wired to the editor. Either pass it through to the editor or remove it from props and deps.

Would you like me to prepare a follow-up diff to remove the prop and its usage?

Also applies to: 35-47, 372-372

apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts (2)

49-54: Avoid unnecessary remote call for custom model.

Only fetch the custom model when the connection type is Custom.

Apply this diff:

-    const llmConnection = await connectorCommands.getLlmConnection();
-    const { type } = llmConnection;
-    const apiBase = llmConnection.connection?.api_base;
-    const customModel = await connectorCommands.getCustomLlmModel();
-    const modelId = type === "Custom" && customModel ? customModel : "gpt-4";
+    const llmConnection = await connectorCommands.getLlmConnection();
+    const { type } = llmConnection;
+    const apiBase = llmConnection.connection?.api_base;
+    let modelId = "gpt-4";
+    if (type === "Custom") {
+      const customModel = await connectorCommands.getCustomLlmModel();
+      modelId = customModel || modelId;
+    }

148-182: Consider typing participants and de-duping action builders.

participants: any[] obscures shape; define a Participant type with full_name. Also, HyprCloud/Custom-with-tools post/pre builders are mostly identical—consider a shared generator to reduce drift.

Also applies to: 213-246, 248-281

apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (3)

26-29: Handle slow fetch UX (optional).

While quickActions load, consider showing DEFAULT_ACTIONS or a lightweight skeleton to avoid an empty list on first render.


115-119: Add accessible alt text for the image.

Empty alt hides the image from screen readers; provide a short descriptive alt or mark it decorative with role="presentation".

Apply this diff:

-        <img
-          src="/assets/dynamic.gif"
-          alt=""
+        <img
+          src="/assets/dynamic.gif"
+          alt="Suggestions"
           className={`${currentSize.icon} mx-auto`}
         />

130-139: Prefer stable keys over index.

If actions reorder, index keys can cause odd re-renders. Use a composite key (e.g., ${action.eventName}:${action.shownTitle}).

apps/desktop/src/components/editor-area/local-search-bar.tsx (1)

15-175: Deduplicate search logic via a shared hook.

This mirrors FloatingSearchBox behavior. Extract a useEditorSearchReplace hook to centralize applySearch, navigation, replace, and scrolling.

apps/desktop/src/components/editor-area/transcript-viewer.tsx (3)

33-49: Replace polling ref-notifier with a callback ref.

The interval loop is unnecessary and wasteful. Use a callback ref to notify parent immediately when the editor mounts.

Apply:

-  // Notify parent when editor ref changes - check periodically for ref to be set
-  useEffect(() => {
-    // Initial notification
-    if (onEditorRefChange) {
-      onEditorRefChange(editorRef.current);
-    }
-
-    // Check if ref gets set later
-    const checkInterval = setInterval(() => {
-      if (editorRef.current?.editor && onEditorRefChange) {
-        onEditorRefChange(editorRef.current);
-        clearInterval(checkInterval);
-      }
-    }, 100);
-
-    return () => clearInterval(checkInterval);
-  }, [onEditorRefChange]);
+  const setEditorRef = useCallback((ref: TranscriptEditorRef | null) => {
+    editorRef.current = ref;
+    onEditorRefChange?.(ref);
+  }, [onEditorRefChange]);
-      <TranscriptEditor
-        ref={editorRef}
+      <TranscriptEditor
+        ref={setEditorRef}
         initialWords={words}
         editable={true}
         onUpdate={handleUpdate}
         c={SpeakerSelector}
       />

Also applies to: 135-141


97-101: Simplify empty-state condition.

sessionId && is redundant since sessionId is a required prop. Minor cleanup.

-  const showEmptyMessage = sessionId && words.length <= 0 && !isLive;
+  const showEmptyMessage = words.length === 0 && !isLive;

191-197: Return null instead of empty

placeholders.

Avoid extra DOM nodes when selector is hidden.

-  if (!sessionId) {
-    return <p></p>;
-  }
+  if (!sessionId) {
+    return null;
+  }
-  if (!inactive) {
-    return <p></p>;
-  }
+  if (!inactive) {
+    return null;
+  }
apps/desktop/src/components/editor-area/index.tsx (1)

363-369: Don’t derive render styles from an imperative ref during render.

tabHeaderRef.current?.isVisible isn’t reactive; the padding may never update when visibility changes.

Minimal, stable alternative:

-          className={cn([
-            activeTab === "transcript"
-              ? "h-full overflow-hidden pt-0"
-              : `h-full overflow-y-auto ${tabHeaderRef.current?.isVisible ? "pt-6" : "pt-3"}`,
-            enhancedContent && activeTab !== "transcript" && "pb-10",
-          ])}
+          className={cn([
+            activeTab === "transcript"
+              ? "h-full overflow-hidden pt-0"
+              : "h-full overflow-y-auto pt-6",
+            enhancedContent && activeTab !== "transcript" && "pb-10",
+          ])}

If dynamic spacing is required, lift isVisible into state via a callback prop from TabHeader instead of reading a ref in render.

apps/desktop/src/components/editor-area/note-header/chips/tag-chip.tsx (1)

65-65: Remove redundant ternary in className.

Both branches return the same class; simplify for clarity.

-            <span className={`truncate ${totalTags === 0 ? "text-neutral-500" : "text-neutral-500"}`}>
+            <span className="truncate text-neutral-500">
apps/desktop/src/components/editor-area/note-header/chips/index.tsx (1)

50-52: Commented-out ShareChip: consider adding a brief “why” or TODO.

Helps avoid long-lived commented code without context.

apps/desktop/src/components/right-panel/index.tsx (1)

20-22: Always rendering ChatView may hide TranscriptView.

If intentional for interim UI, fine; otherwise this removes the ability to switch views.

If temporary, add a brief “why” comment with link/issue to re-enable.

apps/desktop/src/components/editor-area/note-header/index.tsx (1)

27-34: Make “new note” detection robust to markup variants.

Exact equality against "

" is brittle; strip tags and trim before checking.

-  const isNewNote = !sessionTitle?.trim()
-    && (!session.raw_memo_html || session.raw_memo_html === "<p></p>")
-    && (!session.enhanced_memo_html || session.enhanced_memo_html === "<p></p>")
-    && session.words.length === 0;
+  const isNewNote = !sessionTitle?.trim()
+    && (!session.raw_memo_html || session.raw_memo_html.replace(/<[^>]*>/g, "").trim().length === 0)
+    && (!session.enhanced_memo_html || session.enhanced_memo_html.replace(/<[^>]*>/g, "").trim().length === 0)
+    && (session.words?.length ?? 0) === 0;
apps/desktop/src/styles/globals.css (1)

136-148: Use theme tokens for search highlight colors
In apps/desktop/src/styles/globals.css, replace hard-coded #ffeb3b and #31e054 with Tailwind theme colors or CSS custom properties (e.g. theme('colors.yellow.400'), theme('colors.green.500') or corresponding var(--…)).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5175db3 and a8caf3e.

⛔ Files ignored due to path filters (2)
  • apps/desktop/src/locales/en/messages.po is excluded by !**/*.po
  • apps/desktop/src/locales/ko/messages.po is excluded by !**/*.po
📒 Files selected for processing (33)
  • apps/desktop/src/components/editor-area/floating-search-box.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/index.tsx (11 hunks)
  • apps/desktop/src/components/editor-area/local-search-bar.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/metadata-modal.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx (3 hunks)
  • apps/desktop/src/components/editor-area/note-header/chips/index.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (2 hunks)
  • apps/desktop/src/components/editor-area/note-header/chips/tag-chip.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/index.tsx (5 hunks)
  • apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/sub-headers/transcript-sub-header.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/tab-header.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/tab-sub-header.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/note-header/title-input.tsx (3 hunks)
  • apps/desktop/src/components/editor-area/transcript-viewer.tsx (1 hunks)
  • apps/desktop/src/components/pro-gate-modal/index.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (6 hunks)
  • apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (3 hunks)
  • apps/desktop/src/components/right-panel/index.tsx (2 hunks)
  • apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts (1 hunks)
  • apps/desktop/src/components/right-panel/views/chat-view.tsx (1 hunks)
  • apps/desktop/src/components/search-bar.tsx (2 hunks)
  • apps/desktop/src/components/toolbar/bars/main-toolbar.tsx (2 hunks)
  • apps/desktop/src/contexts/right-panel.tsx (1 hunks)
  • apps/desktop/src/routes/app.note.$id.tsx (1 hunks)
  • apps/desktop/src/styles/globals.css (1 hunks)
  • packages/tiptap/src/editor/index.tsx (2 hunks)
  • packages/tiptap/src/shared/extensions.ts (2 hunks)
  • packages/tiptap/src/styles/transcript.css (1 hunks)
  • packages/tiptap/src/transcript/index.tsx (1 hunks)
  • packages/ui/src/components/ui/command.tsx (1 hunks)
  • packages/utils/src/stores/session.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/contexts/right-panel.tsx
  • apps/desktop/src/routes/app.note.$id.tsx
  • apps/desktop/src/components/editor-area/metadata-modal.tsx
  • packages/tiptap/src/transcript/index.tsx
  • apps/desktop/src/components/editor-area/note-header/chips/index.tsx
  • apps/desktop/src/components/editor-area/note-header/chips/tag-chip.tsx
  • apps/desktop/src/components/search-bar.tsx
  • apps/desktop/src/components/editor-area/note-header/tab-sub-header.tsx
  • apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx
  • apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx
  • apps/desktop/src/components/toolbar/bars/main-toolbar.tsx
  • apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts
  • apps/desktop/src/components/editor-area/note-header/index.tsx
  • apps/desktop/src/components/right-panel/views/chat-view.tsx
  • apps/desktop/src/components/editor-area/local-search-bar.tsx
  • packages/utils/src/stores/session.ts
  • apps/desktop/src/components/editor-area/note-header/tab-header.tsx
  • apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx
  • packages/tiptap/src/shared/extensions.ts
  • apps/desktop/src/components/right-panel/components/chat/chat-input.tsx
  • apps/desktop/src/components/editor-area/note-header/title-input.tsx
  • apps/desktop/src/components/editor-area/floating-search-box.tsx
  • apps/desktop/src/components/pro-gate-modal/index.tsx
  • apps/desktop/src/components/editor-area/transcript-viewer.tsx
  • packages/tiptap/src/editor/index.tsx
  • apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx
  • apps/desktop/src/components/right-panel/index.tsx
  • packages/ui/src/components/ui/command.tsx
  • apps/desktop/src/components/editor-area/note-header/sub-headers/transcript-sub-header.tsx
  • apps/desktop/src/components/editor-area/index.tsx
  • apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx
🧬 Code graph analysis (18)
apps/desktop/src/components/editor-area/metadata-modal.tsx (3)
apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx (1)
  • EventChip (37-307)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)
  • ParticipantsChip (53-113)
apps/desktop/src/components/editor-area/note-header/chips/tag-chip.tsx (1)
  • TagChip (20-77)
apps/desktop/src/components/editor-area/note-header/tab-sub-header.tsx (3)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
packages/utils/src/contexts/sessions.tsx (1)
  • useSession (49-74)
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (1)
  • EnhancedNoteSubHeader (23-299)
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (2)
packages/utils/src/contexts/ongoing-session.tsx (1)
  • useOngoingSession (32-46)
apps/desktop/src/utils/template-service.ts (1)
  • TemplateService (5-66)
apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx (1)
packages/utils/src/datetime.ts (1)
  • formatRelativeWithDay (88-132)
apps/desktop/src/components/toolbar/bars/main-toolbar.tsx (1)
apps/desktop/src/components/toolbar/buttons/share-button.tsx (1)
  • ShareButton (28-31)
apps/desktop/src/components/editor-area/note-header/index.tsx (3)
packages/utils/src/contexts/sessions.tsx (1)
  • useSession (49-74)
apps/desktop/src/hooks/enhance-pending.ts (1)
  • useTitleGenerationPendingState (18-30)
apps/desktop/src/components/editor-area/note-header/title-shimmer.tsx (1)
  • TitleShimmer (11-46)
apps/desktop/src/components/right-panel/views/chat-view.tsx (3)
apps/desktop/src/components/right-panel/components/chat/floating-action-buttons.tsx (1)
  • FloatingActionButtons (19-67)
apps/desktop/src/components/right-panel/components/chat/chat-history-view.tsx (1)
  • ChatHistoryView (17-89)
apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (1)
  • EmptyChatState (14-160)
apps/desktop/src/components/editor-area/local-search-bar.tsx (2)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
apps/desktop/src/components/editor-area/note-header/tab-header.tsx (5)
apps/desktop/src/components/editor-area/note-header/index.tsx (2)
  • TabHeaderRef (91-91)
  • TabHeader (90-90)
packages/utils/src/contexts/sessions.tsx (1)
  • useSession (49-74)
packages/utils/src/contexts/ongoing-session.tsx (1)
  • useOngoingSession (32-46)
apps/desktop/src/hooks/enhance-pending.ts (1)
  • useEnhancePendingState (4-16)
packages/ui/src/lib/utils.ts (1)
  • cn (4-6)
apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (2)
packages/ui/src/components/ui/modal.tsx (4)
  • Modal (23-83)
  • ModalBody (99-105)
  • ModalTitle (130-136)
  • ModalDescription (143-148)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
packages/tiptap/src/shared/extensions.ts (1)
packages/tiptap/src/transcript/extensions/search-and-replace.ts (1)
  • SearchAndReplace (229-381)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx (1)
apps/desktop/src/components/right-panel/components/chat-model-info-modal.tsx (1)
  • ChatModelInfoModal (12-134)
apps/desktop/src/components/editor-area/floating-search-box.tsx (2)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
apps/desktop/src/components/editor-area/transcript-viewer.tsx (5)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
apps/desktop/src/components/right-panel/hooks/useTranscript.ts (1)
  • useTranscript (8-91)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
packages/utils/src/contexts/ongoing-session.tsx (1)
  • useOngoingSession (32-46)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)
  • ParticipantsChipInner (115-130)
apps/desktop/src/components/right-panel/components/chat/empty-chat-state.tsx (2)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
apps/desktop/src/components/right-panel/utils/dynamic-quickactions.ts (1)
  • getDynamicQuickActions (33-91)
apps/desktop/src/components/right-panel/index.tsx (2)
apps/desktop/src/contexts/right-panel.tsx (1)
  • useRightPanel (230-236)
apps/desktop/src/components/right-panel/views/chat-view.tsx (1)
  • ChatView (24-349)
apps/desktop/src/components/editor-area/note-header/sub-headers/transcript-sub-header.tsx (2)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
packages/ui/src/components/ui/button.tsx (1)
  • Button (37-89)
apps/desktop/src/components/editor-area/index.tsx (6)
packages/utils/src/contexts/sessions.tsx (1)
  • useSession (49-74)
apps/desktop/src/contexts/right-panel.tsx (1)
  • useRightPanel (230-236)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
apps/desktop/src/components/editor-area/note-header/tab-header.tsx (2)
  • TabHeaderRef (14-16)
  • TabHeader (18-129)
apps/desktop/src/components/editor-area/local-search-bar.tsx (1)
  • LocalSearchBar (15-290)
apps/desktop/src/components/editor-area/transcript-viewer.tsx (1)
  • TranscriptViewer (27-144)
🪛 Biome (2.1.2)
apps/desktop/src/components/right-panel/components/chat/chat-input.tsx

[error] 401-401: This code will never be reached ...

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci (macos, macos-14)
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (24)
apps/desktop/src/components/pro-gate-modal/index.tsx (1)

44-49: Delay increase looks reasonable

The longer wait should give the settings window enough time to mount before the navigation emit fires, reducing the risk of the billing tab navigation being dropped.

apps/desktop/src/components/toolbar/bars/main-toolbar.tsx (1)

60-61: Share button gating looks correct

Re-enabling ShareButton behind the existing isNote guard keeps the hook usage scoped to note routes and restores sharing for note tabs without affecting other contexts. Nice catch.

packages/tiptap/src/shared/extensions.ts (1)

10-10: Approve search-and-replace extension; highlight styles are present
Found .search-result and .search-result-current rules in packages/tiptap/src/styles/transcript.css and apps/desktop/src/styles/globals.css.

apps/desktop/src/components/editor-area/note-header/chips/event-chip.tsx (4)

112-114: Styling tweak LGTM

Neutral-muted icon/text looks consistent with the redesign.


148-153: Meeting vs speech icon + muted date LGTM

Conditional icon + subdued date read well.


253-256: Calendar chip muted style LGTM

Consistent with Event and Onboarding states.


156-157: Remove unsupported date-fns ‘zzz’ token
date-fns (without tz) doesn’t support ‘z/zz/zzz’ and may render literally or throw. Drop the timezone token, use a supported offset (e.g. OOO), or switch to date-fns-tz.

- {format(new Date(date), "EEE, MMM d, yyyy 'at' h:mm a zzz")}
+ {format(new Date(date), "EEE, MMM d, yyyy 'at' h:mm a")}
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (2)

102-104: Muted icon/text LGTM

Matches the new visual tone for chips.


115-130: Exporting ParticipantsChipInner LGTM. Verified import only in apps/desktop/src/components/editor-area/transcript-viewer.tsx.

packages/tiptap/src/styles/transcript.css (1)

34-36: Verify transcript rendering under pre-wrap + break-word
.transcript-speaker-content remains inline and inherits pre-wrap/break-word; manually check for layout regressions with long words, pasted content, and search highlights.

apps/desktop/src/components/editor-area/floating-search-box.tsx (2)

15-304: Overall LGTM.

Robust editor resolution, keyboard handling, and result syncing. Good use of storage and safe fallbacks.


40-61: Verified useDebouncedCallback signature
beautiful-react-hooks defines useDebouncedCallback(fn, dependencies?, wait?, options?), so passing [] and 300 correctly applies debouncing.

apps/desktop/src/components/editor-area/local-search-bar.tsx (1)

214-289: Nice integration and UX.

Focus management, compact UI, and keyboard nav feel solid.

apps/desktop/src/components/editor-area/note-header/sub-headers/transcript-sub-header.tsx (2)

16-21: LGTM on polling and conditional UI.

Polling every 2.5s with enabled + queryKey scoping is fine; button visibility correctly follows data presence.

Also applies to: 45-55


6-6: Remove unused editorRef prop and TranscriptEditorRef import. No active call sites; drop the import, the editorRef line in TranscriptSubHeaderProps, and its parameter in the function signature.

apps/desktop/src/components/editor-area/index.tsx (1)

302-309: Search integration wiring looks good.

Hotkey handler, tab-aware editorRef routing, and reset via key={activeTab} are consistent. No unused vars observed.

Also applies to: 336-343, 381-399

apps/desktop/src/components/editor-area/note-header/chips/tag-chip.tsx (1)

60-60: Icon color normalization looks good.

Unifying the TagsIcon color to text-neutral-500 matches the broader muted scheme.

packages/tiptap/src/transcript/index.tsx (1)

185-188: Padding/min-height adjustments LGTM.

Improves scrollable area and content spacing without affecting logic.

apps/desktop/src/components/right-panel/index.tsx (1)

4-8: Import/usage cleanup for ChatView is fine.

No unused imports; minimal surface.

apps/desktop/src/components/editor-area/note-header/index.tsx (4)

10-11: Re-exporting TabHeader/TabSubHeader looks good.

Keeps consumers co-located with NoteHeader.


59-61: Spacing tweak LGTM.

Reduced bottom padding aligns with new sub-header density.


71-71: Auto-focus on new notes is a good UX touch.

Conditioned by editable; safe.


89-92: Export surface additions LGTM.

Keeps API tidy and discoverable from the index.

packages/utils/src/stores/session.ts (1)

47-60: session.ts only mutates showRaw/activeTab in setActiveTab
There are no setShowRaw, updateRawNote or updateEnhancedNote actions—so no desynchronization risk.

Likely an incorrect or invalid review comment.

Comment on lines +18 to +20
export const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>(
({ sessionId, onEnhance, isEnhancing, progress = 0, showProgress = false }, ref) => {
const [activeTab, setActiveTab] = useSession(sessionId, (s) => [
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove/alias unused props in TabHeader.

onEnhance, progress, and showProgress are not used here. Alias to underscores to avoid unused bindings.

-export const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>(
-  ({ sessionId, onEnhance, isEnhancing, progress = 0, showProgress = false }, ref) => {
+export const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>(
+  ({ sessionId, onEnhance: _onEnhance, isEnhancing, progress: _progress = 0, showProgress: _showProgress = false }, ref) => {

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>(
({ sessionId, onEnhance, isEnhancing, progress = 0, showProgress = false }, ref) => {
const [activeTab, setActiveTab] = useSession(sessionId, (s) => [
export const TabHeader = forwardRef<TabHeaderRef, TabHeaderProps>(
({ sessionId, onEnhance: _onEnhance, isEnhancing, progress: _progress = 0, showProgress: _showProgress = false }, ref) => {
const [activeTab, setActiveTab] = useSession(sessionId, (s) => [
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/note-header/tab-header.tsx around
lines 18 to 20, alias the unused props to underscore-prefixed names in the
component parameter destructuring to avoid unused binding warnings — e.g. change
onEnhance to onEnhance: _onEnhance, progress to progress: _progress (keep
default = 0), and showProgress to showProgress: _showProgress — leave
isEnhancing and sessionId unchanged so functionality is preserved and ESLint
no-unused-vars is satisfied.

Comment on lines +15 to +17
export function TabSubHeader(
{ sessionId, onEnhance, isEnhancing, transcriptEditorRef, progress, showProgress, hashtags }: TabSubHeaderProps,
) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Silence unused props to satisfy no-unused-vars.

transcriptEditorRef and hashtags aren’t used (transcript path is commented out). Alias them to underscores in the destructuring.

-export function TabSubHeader(
-  { sessionId, onEnhance, isEnhancing, transcriptEditorRef, progress, showProgress, hashtags }: TabSubHeaderProps,
-) {
+export function TabSubHeader(
+  { sessionId, onEnhance, isEnhancing, transcriptEditorRef: _transcriptEditorRef, progress, showProgress, hashtags: _hashtags }: TabSubHeaderProps,
+) {

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function TabSubHeader(
{ sessionId, onEnhance, isEnhancing, transcriptEditorRef, progress, showProgress, hashtags }: TabSubHeaderProps,
) {
export function TabSubHeader(
{ sessionId, onEnhance, isEnhancing, transcriptEditorRef: _transcriptEditorRef, progress, showProgress, hashtags: _hashtags }: TabSubHeaderProps,
) {
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/note-header/tab-sub-header.tsx around
lines 15 to 17, the props transcriptEditorRef and hashtags are unused and
trigger no-unused-vars; silence them by aliasing in the function parameter
destructuring (e.g., rename to _transcriptEditorRef and _hashtags) so linters
accept unused values while preserving the prop positions and types.

Comment on lines +396 to +404
switch (connectionType) {
case "Custom":
return customLlmModelQuery.data || "Custom Model";
case "HyprLocal":
return "Local LLM";
return "HyprLLM";
default:
return "Model";
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove unreachable return (Biome error) and verify intended case.

In getCurrentModelName, the second return inside the "HyprLocal" case is unreachable.

Apply this diff:

   switch (connectionType) {
     case "Custom":
       return customLlmModelQuery.data || "Custom Model";
     case "HyprLocal":
       return "Local LLM";
-      return "HyprLLM";
     default:
       return "Model";
   }

If "HyprLLM" is an actual connection type, add an explicit case instead of the unreachable return.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
switch (connectionType) {
case "Custom":
return customLlmModelQuery.data || "Custom Model";
case "HyprLocal":
return "Local LLM";
return "HyprLLM";
default:
return "Model";
}
switch (connectionType) {
case "Custom":
return customLlmModelQuery.data || "Custom Model";
case "HyprLocal":
return "Local LLM";
default:
return "Model";
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 401-401: This code will never be reached ...

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

🤖 Prompt for AI Agents
In apps/desktop/src/components/right-panel/components/chat/chat-input.tsx around
lines 396 to 404, the "HyprLocal" switch case contains an unreachable second
return ("HyprLLM"); remove the unreachable return and decide intended behavior:
either return "HyprLocal" (e.g., "Local LLM") only, or if "HyprLLM" is a valid
connectionType add an explicit case 'HyprLLM' that returns "HyprLLM" and keep
'HyprLocal' returning "Local LLM"; update tests or usages if needed.

import "../styles/tiptap.css";
import "../styles/mention.css";

import Document from "@tiptap/extension-document";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove duplicate Document extension to avoid schema/extension conflicts

StarterKit (in shared.extensions) already brings Document. Adding Document again can cause duplicate extension name/schema conflicts. Register Document only once.

Apply this diff to remove the duplicate:

-import Document from "@tiptap/extension-document";
@@
       extensions: [
         ...shared.extensions,
-        Document,
         mention(mentionConfig),
       ],

If you need a custom Document schema, disable it in StarterKit where it’s configured and add Document once there instead (shared/extensions.ts).

Also applies to: 36-38

🤖 Prompt for AI Agents
In packages/tiptap/src/editor/index.tsx around lines 4 (and similarly lines
36-38), you import Document from "@tiptap/extension-document" while StarterKit
(shared.extensions) already provides Document, causing duplicate
extension/schema conflicts; remove the duplicate import(s) and any additional
registration of Document here so Document is registered only once (either rely
on StarterKit or, if you need a custom Document, disable it in StarterKit in
shared/extensions.ts and add a single Document import/registration there).

className={cn(
"fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2",
"w-[90vw] max-w-[450px]",
"w-[2000px] max-w-[450px]",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fixed 2000px width breaks responsiveness; restore viewport-based width

Using w-[2000px] with max-w-[450px] forces ~450px width even on narrow viewports, causing overflow. Restore responsive width.

-          "w-[2000px] max-w-[450px]",
+          "w-[90vw] max-w-[450px]",
🤖 Prompt for AI Agents
In packages/ui/src/components/ui/command.tsx around line 72, the hardcoded
"w-[2000px] max-w-[450px]" forces the element to stay ~450px and overflow on
narrow viewports; replace the fixed 2000px with a responsive width such as
"w-full max-w-[450px]" (or simply remove the w-[2000px] token) so the component
uses the viewport width up to the 450px max; update the class string
accordingly.

Comment on lines +31 to 32
activeTab: "raw",
get,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialize activeTab from existing session state to avoid drift.

If enhanced content exists, start on “enhanced”; otherwise “raw”.

-    activeTab: "raw",
+    activeTab: session.enhanced_memo_html ? "enhanced" : "raw",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
activeTab: "raw",
get,
activeTab: session.enhanced_memo_html ? "enhanced" : "raw",
get,
🤖 Prompt for AI Agents
In packages/utils/src/stores/session.ts around lines 31-32, replace the
hardcoded activeTab: "raw" with a computed initialization that reads the current
store/session state via get() and sets activeTab to "enhanced" when enhanced
content exists, otherwise "raw" (e.g. activeTab: get()?.session?.enhancedContent
? "enhanced" : "raw"), so the tab reflects existing session state and avoids
drift.

Copy link

@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: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8caf3e and 2c9d363.

📒 Files selected for processing (2)
  • apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (1 hunks)
  • apps/desktop/src/components/editor-area/transcript-viewer.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,tsx,rs}

⚙️ CodeRabbit configuration file

**/*.{js,ts,tsx,rs}: 1. Do not add any error handling. Keep the existing one.
2. No unused imports, variables, or functions.
3. For comments, keep it minimal. It should be about "Why", not "What".

Files:

  • apps/desktop/src/components/editor-area/transcript-viewer.tsx
  • apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx
🧬 Code graph analysis (2)
apps/desktop/src/components/editor-area/transcript-viewer.tsx (5)
packages/tiptap/src/transcript/index.tsx (1)
  • TranscriptEditorRef (33-41)
apps/desktop/src/components/right-panel/hooks/useTranscript.ts (1)
  • useTranscript (8-91)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (63-69)
packages/utils/src/contexts/ongoing-session.tsx (1)
  • useOngoingSession (32-46)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)
  • ParticipantsChipInner (115-130)
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx (2)
packages/utils/src/contexts/ongoing-session.tsx (1)
  • useOngoingSession (32-46)
apps/desktop/src/utils/template-service.ts (1)
  • TemplateService (5-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci (macos, macos-14)

Comment on lines +15 to +29
interface EnhancedNoteSubHeaderProps {
sessionId: string;
onEnhance?: (params: { triggerType: "manual" | "template"; templateId?: string | null }) => void;
isEnhancing?: boolean;
progress?: number;
showProgress?: boolean;
}

export function EnhancedNoteSubHeader({
sessionId,
onEnhance,
isEnhancing,
progress = 0,
showProgress = false,
}: EnhancedNoteSubHeaderProps) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Drop the unused sessionId prop

sessionId is still declared in the props and destructured in the component, but nothing ever reads it. Our lint rules reject unused values, so this needs to be removed (or actually used) before we can merge.

-interface EnhancedNoteSubHeaderProps {
-  sessionId: string;
+interface EnhancedNoteSubHeaderProps {
   onEnhance?: (params: { triggerType: "manual" | "template"; templateId?: string | null }) => void;
   isEnhancing?: boolean;
   progress?: number;
   showProgress?: boolean;
 }
 
 export function EnhancedNoteSubHeader({
-  sessionId,
   onEnhance,
   isEnhancing,
   progress = 0,
   showProgress = false,
 }: EnhancedNoteSubHeaderProps) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface EnhancedNoteSubHeaderProps {
sessionId: string;
onEnhance?: (params: { triggerType: "manual" | "template"; templateId?: string | null }) => void;
isEnhancing?: boolean;
progress?: number;
showProgress?: boolean;
}
export function EnhancedNoteSubHeader({
sessionId,
onEnhance,
isEnhancing,
progress = 0,
showProgress = false,
}: EnhancedNoteSubHeaderProps) {
interface EnhancedNoteSubHeaderProps {
onEnhance?: (params: { triggerType: "manual" | "template"; templateId?: string | null }) => void;
isEnhancing?: boolean;
progress?: number;
showProgress?: boolean;
}
export function EnhancedNoteSubHeader({
onEnhance,
isEnhancing,
progress = 0,
showProgress = false,
}: EnhancedNoteSubHeaderProps) {
// …rest of implementation…
}
🤖 Prompt for AI Agents
In
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx
around lines 15 to 29, the sessionId prop is declared and destructured but never
used, causing lint failures for unused variables; remove sessionId from the
EnhancedNoteSubHeaderProps interface and from the component parameter list (and
any related defaulting) so only used props remain, or alternatively use it if
intended — but for this change delete the unused prop and update any callers if
necessary.

Comment on lines +172 to +225
<Popover open={isTemplateDropdownOpen} onOpenChange={setIsTemplateDropdownOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={isEnhancing ? handleRegenerateOrCancel : () => setIsTemplateDropdownOpen(true)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
disabled={false}
className="text-xs h-[28px] px-3 hover:bg-neutral-100 relative w-[180px] shadow-sm"
>
{/* Main content - centered in available space */}
<div className="absolute inset-0 flex items-center justify-center pr-6">
<div className="flex items-center">
{isEnhancing
? (
isHovered
? (
<>
<div className="mr-1.5 w-3 h-3 flex items-center justify-center">
<XIcon size={12} className="text-red-600" />
</div>
<span className="text-red-600">Cancel</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
: (
<>
<Spinner className="mr-1.5 w-3 h-3" />
<span>Generating...</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
)
: (
<>
<RefreshCwIcon size={14} className="mr-1.5" />
Regenerate
</>
)}
</div>
</div>

{/* Chevron - fixed position on right */}
<ChevronDownIcon size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400" />
</Button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Prevent the template popover from opening while cancelling

When isEnhancing is true and the user clicks to cancel, Radix still fires onOpenChange(true), so the template picker opens right after the cancel request. Likewise, because the trigger’s onClick always forces setIsTemplateDropdownOpen(true), the dropdown can’t be closed by clicking the button again. That’s a confusing UX regression. Gate the onOpenChange callback while enhancing and stop forcing the open state in the click handler so the popover behaves like a normal toggle.

-        <Popover open={isTemplateDropdownOpen} onOpenChange={setIsTemplateDropdownOpen}>
+        <Popover
+          open={isTemplateDropdownOpen}
+          onOpenChange={nextOpen => {
+            if (isEnhancing) {
+              return;
+            }
+            setIsTemplateDropdownOpen(nextOpen);
+          }}
+        >
           <PopoverTrigger asChild>
             <Button
               variant="outline"
               size="sm"
-              onClick={isEnhancing ? handleRegenerateOrCancel : () => setIsTemplateDropdownOpen(true)}
+              onClick={event => {
+                if (isEnhancing) {
+                  event.preventDefault();
+                  handleRegenerateOrCancel();
+                }
+              }}
               onMouseEnter={() => setIsHovered(true)}
               onMouseLeave={() => setIsHovered(false)}
               disabled={false}
               className="text-xs h-[28px] px-3 hover:bg-neutral-100 relative w-[180px] shadow-sm"
             >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Popover open={isTemplateDropdownOpen} onOpenChange={setIsTemplateDropdownOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={isEnhancing ? handleRegenerateOrCancel : () => setIsTemplateDropdownOpen(true)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
disabled={false}
className="text-xs h-[28px] px-3 hover:bg-neutral-100 relative w-[180px] shadow-sm"
>
{/* Main content - centered in available space */}
<div className="absolute inset-0 flex items-center justify-center pr-6">
<div className="flex items-center">
{isEnhancing
? (
isHovered
? (
<>
<div className="mr-1.5 w-3 h-3 flex items-center justify-center">
<XIcon size={12} className="text-red-600" />
</div>
<span className="text-red-600">Cancel</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
: (
<>
<Spinner className="mr-1.5 w-3 h-3" />
<span>Generating...</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
)
: (
<>
<RefreshCwIcon size={14} className="mr-1.5" />
Regenerate
</>
)}
</div>
</div>
{/* Chevron - fixed position on right */}
<ChevronDownIcon size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400" />
</Button>
<Popover
open={isTemplateDropdownOpen}
onOpenChange={nextOpen => {
if (isEnhancing) {
return;
}
setIsTemplateDropdownOpen(nextOpen);
}}
>
<PopoverTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={event => {
if (isEnhancing) {
event.preventDefault();
handleRegenerateOrCancel();
}
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
disabled={false}
className="text-xs h-[28px] px-3 hover:bg-neutral-100 relative w-[180px] shadow-sm"
>
{/* Main content - centered in available space */}
<div className="absolute inset-0 flex items-center justify-center pr-6">
<div className="flex items-center">
{isEnhancing
? (
isHovered
? (
<>
<div className="mr-1.5 w-3 h-3 flex items-center justify-center">
<XIcon size={12} className="text-red-600" />
</div>
<span className="text-red-600">Cancel</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
: (
<>
<Spinner className="mr-1.5 w-3 h-3" />
<span>Generating...</span>
{shouldShowProgress && (
<span className="ml-2 text-xs font-mono">
{Math.round(progress * 100)}%
</span>
)}
</>
)
)
: (
<>
<RefreshCwIcon size={14} className="mr-1.5" />
Regenerate
</>
)}
</div>
</div>
{/* Chevron - fixed position on right */}
<ChevronDownIcon size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-400" />
</Button>
🤖 Prompt for AI Agents
In
apps/desktop/src/components/editor-area/note-header/sub-headers/enhanced-note-sub-header.tsx
around lines 172-225, Radix Popover is opening when the user clicks to cancel
because onOpenChange and the trigger onClick always force open; change
onOpenChange to ignore changes while isEnhancing (e.g. onOpenChange={(open) => {
if (!isEnhancing) setIsTemplateDropdownOpen(open) }}), and update the Button
onClick to stop forcing true (e.g. onClick={() => { if (isEnhancing)
handleRegenerateOrCancel(); else setIsTemplateDropdownOpen(prev => !prev); }}),
so the popover behaves as a normal toggle and will not open during cancel.

Comment on lines +192 to +197
useEffect(() => {
if (human) {
onSpeakerChange(human, speakerRange);
}
}, [human, speakerRange]);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Include onSpeakerChange in the dependency list

This effect closes over onSpeakerChange but does not list it as a dependency, so if the editor swaps out that handler (e.g., when the transcript editor remounts or the session changes), we’ll keep invoking the stale callback and silently drop new updates. Please add onSpeakerChange to the dependency array so the effect reacts to handler changes.

   useEffect(() => {
     if (human) {
       onSpeakerChange(human, speakerRange);
     }
-  }, [human, speakerRange]);
+  }, [human, speakerRange, onSpeakerChange]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (human) {
onSpeakerChange(human, speakerRange);
}
}, [human, speakerRange]);
useEffect(() => {
if (human) {
onSpeakerChange(human, speakerRange);
}
}, [human, speakerRange, onSpeakerChange]);
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/transcript-viewer.tsx around lines
192 to 197, the useEffect closes over onSpeakerChange but doesn't list it in
dependencies; update the effect to include onSpeakerChange in the dependency
array so the effect re-runs when the handler changes (i.e., change the
dependency list from [human, speakerRange] to [human, speakerRange,
onSpeakerChange]) to avoid calling a stale callback.

Comment on lines +198 to +205
useEffect(() => {
const foundHuman = participants.find((s) => s.id === speakerId);

if (foundHuman) {
setHuman(foundHuman);
}
}, [participants, speakerId]);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset human when the current speaker disappears

When the assigned speaker ID no longer matches any participant (e.g., speaker removed or cleared), we leave the previous human in state, so the UI still displays and emits changes for the wrong person. Make sure we also clear the human state when we don’t find a match.

   useEffect(() => {
     const foundHuman = participants.find((s) => s.id === speakerId);

     if (foundHuman) {
       setHuman(foundHuman);
+    } else {
+      setHuman(null);
     }
   }, [participants, speakerId]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const foundHuman = participants.find((s) => s.id === speakerId);
if (foundHuman) {
setHuman(foundHuman);
}
}, [participants, speakerId]);
useEffect(() => {
const foundHuman = participants.find((s) => s.id === speakerId);
if (foundHuman) {
setHuman(foundHuman);
} else {
setHuman(null);
}
}, [participants, speakerId]);
🤖 Prompt for AI Agents
In apps/desktop/src/components/editor-area/transcript-viewer.tsx around lines
198 to 205, the useEffect only sets `human` when a matching participant is found
but does not clear it when no match exists; update the effect so that if
`participants.find(s => s.id === speakerId)` returns undefined you call
`setHuman(null)` (or `setHuman(undefined)` consistent with the component's state
type) to clear the previous value, keeping the UI and emitted changes in sync
with the current participant list.

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