Skip to content

transcript-editor-ui#850

Merged
yujonglee merged 31 commits intomainfrom
transcript-editor-2
May 22, 2025
Merged

transcript-editor-ui#850
yujonglee merged 31 commits intomainfrom
transcript-editor-2

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented May 20, 2025

Summary by CodeRabbit

  • New Features

    • Added a speaker selection popover to the transcript editor, allowing users to change speakers dynamically.
    • Transcript editor now supports live updates and exposes new content update and programmatic control features.
  • Refactor

    • Participant chips in the editor area are now grouped by organization for improved clarity.
    • Transcript view simplified to always show the editor, with editability controlled by session status.
    • Speaker node architecture improved for more flexible speaker management.
    • Speaker view component refactored to separate UI rendering from data logic.
  • Style

    • Transcript word styling updated for a cleaner look and improved interaction feedback.
    • Popover components refined for better accessibility and visual consistency.
  • Bug Fixes

    • Removed obsolete translation strings and updated source references in localization files.
  • Chores

    • Removed unused dependencies and context providers, including TinyBase integration.
    • Updated and added dependencies for editor and utility packages.
    • Cleaned up seed data for development environments.
  • Tests

    • Enhanced editor content conversion tests for greater reliability.
  • Documentation

    • Improved type safety and documentation for editor utilities and components.

@yujonglee yujonglee force-pushed the transcript-editor-2 branch from d898681 to 7f158f7 Compare May 21, 2025 06:03
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 21, 2025

Walkthrough

The changes remove the TinyBase state management integration from the desktop app, including its provider, context, and dependencies. Participant and transcript components are refactored for improved data flow and UI, with new hooks and components introduced for participant grouping and speaker selection. The Tiptap transcript editor is enhanced with richer speaker node handling, a more flexible API, and improved content synchronization. CSS and UI components receive styling and accessibility updates. Localization files and database seeding are updated for consistency.

Changes

File(s) Change Summary
apps/desktop/package.json
packages/tiptap/package.json
Removed TinyBase dependency from desktop; added several dependencies to tiptap including utils, router, and editor extensions.
apps/desktop/src/contexts/tinybase.tsx Deleted TinyBaseProvider context and all TinyBase state, persister, and relationship logic.
apps/desktop/src/contexts/index.ts Removed export of TinyBase context.
apps/desktop/src/routes/app.tsx Removed TinyBaseProvider from the React provider tree; no other provider or logic changes.
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx Refactored participant fetching/grouping into a hook; introduced unified participant list rendering; simplified event handling and UI.
apps/desktop/src/components/right-panel/views/transcript-view.tsx Removed manual editing toggle; always mounts TranscriptEditor; added speaker selection popover; simplified transcript rendering.
packages/tiptap/src/transcript/index.tsx TranscriptEditor now requires initialWords, adds speaker view component prop, onUpdate callback, and ref with setWords; BubbleMenu extension added.
packages/tiptap/src/transcript/nodes.ts Refactored SpeakerNode to a factory with richer attributes and comprehensive command API; added module augmentation for commands.
packages/tiptap/src/transcript/views.tsx Replaced SpeakerView with createSpeakerView factory; decoupled UI/data fetching from node view logic; added new types.
packages/tiptap/src/transcript/utils.ts
packages/tiptap/src/transcript/utils.test.ts
Generalized editor content types, made attribute keys constants, improved type safety, and added explicit test assertions for content structure.
packages/tiptap/src/styles/transcript.css Simplified transcript word hover/selected styles; removed pointer cursor and visual highlights in non-editable contexts.
packages/ui/src/components/ui/popover.tsx PopoverTrigger refactored to forwardRef with default class names; PopoverContent focus ring styles simplified.
apps/desktop/src/locales/en/messages.po
apps/desktop/src/locales/ko/messages.po
Updated source code reference line numbers; marked "Remove {0} from list" as obsolete in both locales.
crates/db-user/src/init.rs Removed "John Jeong" from the seeded humans in the database initializer.
apps/desktop/src/components/editor-area/note-header/listen-button.tsx Removed unused useRef import and related effect for initial mount tracking.
plugins/listener/src/fsm.rs Updated session word update flow to await session update before emitting events; changed update function to return updated words.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TranscriptView
    participant TranscriptEditor
    participant SpeakerSelector

    User->>TranscriptView: Opens transcript
    TranscriptView->>TranscriptEditor: Mounts with initialWords, speaker view component, onUpdate
    TranscriptEditor->>SpeakerSelector: Renders speaker popover for each speaker node
    User->>SpeakerSelector: Selects/changing speaker
    SpeakerSelector->>TranscriptEditor: Updates speaker node attributes
    TranscriptEditor->>TranscriptView: Calls onUpdate with new words
    TranscriptView->>Session: Saves updated words
Loading
sequenceDiagram
    participant ParticipantsChip
    participant useParticipantsWithOrg
    participant ParticipantsChipInner

    ParticipantsChip->>useParticipantsWithOrg: Fetch grouped participants by org
    useParticipantsWithOrg-->>ParticipantsChip: Returns grouped data
    ParticipantsChip->>ParticipantsChipInner: Passes grouped participants and click handler
    ParticipantsChipInner->>User: Renders participant groups and handles UI events
Loading

Poem

A hop and a skip, TinyBase is gone,
New hooks and popovers—our code hops on!
Speakers now switch with a gentle click,
Participants grouped, the UI feels slick.
With Tiptap enhanced and the styles refined,
This bunny’s quite proud of the changes combined!
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c65375c and 697552d.

📒 Files selected for processing (4)
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/views/transcript-view.tsx (7 hunks)
  • packages/tiptap/src/transcript/nodes.ts (2 hunks)
  • plugins/listener/src/fsm.rs (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/components/editor-area/note-header/listen-button.tsx
🔇 Additional comments (9)
plugins/listener/src/fsm.rs (3)

259-269: Improved synchronization of word updates

The refactored code now properly awaits the result of update_session before emitting the event, ensuring that the SessionEvent contains the fully updated word list. This eliminates potential race conditions and data inconsistencies that could occur in the previous implementation.


365-369: Improved return type for better data flow

The updated function signature now returns the full list of words, allowing callers to use the comprehensive word list after updating. This creates a more cohesive and reliable data flow throughout the system.


382-382: Return updated words from database operation

This change properly returns the updated word list from the session, providing the complete context to callers and ensuring data consistency.

apps/desktop/src/components/right-panel/views/transcript-view.tsx (3)

32-38: Well-typed editor reference with proper synchronization

The introduction of a properly typed editorRef with the effect to sync words when they change ensures consistent state between the component and the editor.


133-139: Clean component composition with clear prop separation

The TranscriptEditor component now receives clear, well-defined props including the ref, initial words, editability control, update callback, and speaker component. This is a good pattern that allows for better component composition and clearer data flow.


197-249: Well-designed SpeakerSelector component

The SpeakerSelector component provides a good user experience for changing speakers with:

  1. Clear UI feedback through the Popover component
  2. Prevention of editor focus issues with the mouseDown handler
  3. Proper integration with the participants data
  4. Good handling of speaker display fallback

This approach effectively decouples the speaker selection UI from the editor while maintaining a cohesive user experience.

packages/tiptap/src/transcript/nodes.ts (3)

8-44: Well-structured command interface declaration

The TypeScript module declaration properly extends the tiptap Commands interface with a comprehensive set of speaker-related commands. This provides good type safety and documentation for users of the library.


172-226: Excellent factory pattern for SpeakerNode

The SpeakerNode factory function is a great pattern that allows for injecting a custom component for speaker rendering while encapsulating the node's functionality. This significantly improves the flexibility and composability of the transcript editor.


270-309: Effective keyboard shortcut implementation for word deletion

The Backspace keyboard shortcut handler provides an efficient way to delete multiple selected word nodes in a single transaction. This improves the user experience when editing transcripts by making text selection and deletion behave more naturally.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 5

🔭 Outside diff range comments (1)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)

154-162: 🛠️ Refactor suggestion

Clickable <div> should be a <button> for a11y & semantics.

The whole row is interactive but rendered as a generic div.
Screen-reader users won’t be informed that it is clickable, and keyboard users cannot activate it via Space/Enter (except through nested hacks).

Replace with a <button> or add role="button" and key handlers for Enter/Space on the container.

-<div
+<button
   className="flex items-center …"
   onClick={() => handleClickHuman(member)}
+  onKeyDown={(e) => { if (['Enter', ' '].includes(e.key)) handleClickHuman(member) }}
+  role="button"
+  tabIndex={0}
 >
🧹 Nitpick comments (7)
apps/desktop/src/components/right-panel/views/exp.tsx (1)

33-35: Consider adding a more informative empty state

The current implementation returns an empty paragraph when no session ID is found. Consider providing a more informative empty state or error message to improve user experience.

  if (!sessionId) {
-    return <p></p>;
+    return <p className="text-neutral-400 text-sm">No active session</p>;
  }
packages/tiptap/src/transcript/extensions.ts (2)

4-4: Prefer import type to avoid clashing with the DOM-global Node.

Node is already declared in the DOM lib. Importing the same identifier as a value pollutes the bundle and can confuse tooling.
Switch to a type-only import and give it an alias for clarity.

-import { Node } from "prosemirror-model";
+import type { Node as PMNode } from "prosemirror-model";

Then replace occurrences of Node in this file with PMNode.

🧰 Tools
🪛 GitHub Actions: .github/workflows/desktop_ci.yaml

[error] 4-4: TypeScript error TS2307: Cannot find module 'prosemirror-model' or its corresponding type declarations.


185-349: Consider splitting the monolithic SpeakerCommands extension for maintainability.

The extension now hosts 5 fairly large command bodies (≈160 lines). Future updates will be cumbersome and the file is already dense.

Suggested plan:

  1. Move pure helpers (e.g., the descendant traversal & update pattern) into a util: updateSpeakerNodes(tr, predicate, updater).
  2. Keep each high-level command in its own file or at least its own object literal to improve discoverability.
  3. Re-export a composed extension from index.ts.

Not a blocker but will pay off quickly as the command surface grows.

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

29-35: reduce on a plain object risks prototype leakage – use Map.

Using {} as an accumulator without Object.create(null) inherits Object.prototype keys (toString, etc.) which could collide with organisation IDs like "toString".
A Map<string, Human[]> is safer and semantically clearer.

packages/tiptap/src/transcript/utils.ts (1)

78-110: Type guard can be simplified & made safer with in operator.

Manually indexing attrs[SPEAKER_ID_ATTR] and coercing may return '' (empty string) which is falsy but still truthy for identity existence.
Using an in check prevents accidental empty-string acceptance.

-if (attrs[SPEAKER_ID_ATTR]) {
+if (SPEAKER_ID_ATTR in attrs && attrs[SPEAKER_ID_ATTR] !== null) {

Also add similar guards for the index attr.

packages/tiptap/src/transcript/nodes.ts (2)

115-136: Traversal logic scans the whole document even when only “before position” nodes are relevant

descendants keeps walking siblings after return false; it merely skips the current node’s children.
For large docs this is wasted work.

A cheaper alternative:

tr.doc.nodesBetween(0, position, (node, pos) => {
  /* same update logic */
});

This bounds the traversal to exactly the range you care about.
Consider applying the same optimisation to replaceSpeakerIdsAfter.


216-218: Avoid any in addCommands

Casting the whole object defeats the purpose of the strongly-typed command interface you extended above.
Expose the commands with proper generics instead:

-    addCommands() {
-      return implementCommands as any;
-    },
+    addCommands() {
+      return implementCommands as typeof implementCommands;
+    },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 573d5ef and 70b1cac.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/desktop/package.json (0 hunks)
  • apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (3 hunks)
  • apps/desktop/src/components/right-panel/views/exp.tsx (1 hunks)
  • apps/desktop/src/components/right-panel/views/transcript-view.tsx (6 hunks)
  • apps/desktop/src/contexts/index.ts (0 hunks)
  • apps/desktop/src/contexts/tinybase.tsx (0 hunks)
  • apps/desktop/src/locales/en/messages.po (3 hunks)
  • apps/desktop/src/locales/ko/messages.po (3 hunks)
  • apps/desktop/src/routes/app.tsx (1 hunks)
  • packages/tiptap/package.json (1 hunks)
  • packages/tiptap/src/styles/transcript.css (2 hunks)
  • packages/tiptap/src/transcript/extensions.ts (2 hunks)
  • packages/tiptap/src/transcript/index.tsx (3 hunks)
  • packages/tiptap/src/transcript/nodes.ts (1 hunks)
  • packages/tiptap/src/transcript/utils.test.ts (1 hunks)
  • packages/tiptap/src/transcript/utils.ts (6 hunks)
  • packages/tiptap/src/transcript/views.tsx (1 hunks)
  • packages/ui/src/components/ui/popover.tsx (2 hunks)
💤 Files with no reviewable changes (3)
  • apps/desktop/package.json
  • apps/desktop/src/contexts/index.ts
  • apps/desktop/src/contexts/tinybase.tsx
🧰 Additional context used
🧬 Code Graph Analysis (6)
packages/tiptap/src/transcript/utils.test.ts (1)
packages/tiptap/src/transcript/utils.ts (2)
  • fromWordsToEditor (35-76)
  • fromEditorToWords (78-126)
packages/ui/src/components/ui/popover.tsx (1)
packages/ui/src/lib/utils.ts (1)
  • cn (4-6)
packages/tiptap/src/transcript/views.tsx (2)
packages/tiptap/src/transcript/index.tsx (1)
  • SpeakerViewInnerProps (17-17)
packages/tiptap/src/editor/index.tsx (1)
  • TiptapEditor (10-10)
apps/desktop/src/components/right-panel/views/exp.tsx (5)
packages/tiptap/src/transcript/index.tsx (1)
  • SpeakerViewInnerProps (17-17)
packages/tiptap/src/transcript/views.tsx (1)
  • SpeakerViewInnerProps (31-37)
plugins/db/js/bindings.gen.ts (1)
  • Human (152-152)
packages/ui/src/components/ui/popover.tsx (3)
  • Popover (85-85)
  • PopoverTrigger (85-85)
  • PopoverContent (85-85)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (1)
  • ParticipantsChipInner (91-126)
packages/tiptap/src/transcript/index.tsx (3)
packages/tiptap/src/transcript/utils.ts (3)
  • Word (4-4)
  • fromEditorToWords (78-126)
  • fromWordsToEditor (35-76)
packages/tiptap/src/transcript/views.tsx (1)
  • SpeakerViewInnerComponent (39-39)
packages/tiptap/src/transcript/nodes.ts (1)
  • SpeakerNode (172-220)
apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx (3)
plugins/db/js/bindings.gen.ts (1)
  • Human (152-152)
apps/desktop/src/contexts/hypr.tsx (1)
  • useHypr (48-54)
packages/ui/src/components/ui/popover.tsx (2)
  • PopoverContent (85-85)
  • Popover (85-85)
🪛 GitHub Actions: .github/workflows/desktop_ci.yaml
packages/tiptap/src/transcript/extensions.ts

[error] 4-4: TypeScript error TS2307: Cannot find module 'prosemirror-model' or its corresponding type declarations.

🔇 Additional comments (28)
apps/desktop/src/routes/app.tsx (1)

44-88: TinyBaseProvider successfully removed

The TinyBase context provider has been removed from the component tree, aligning with the broader effort to refactor state management by removing TinyBase integration. The component hierarchy remains properly nested with no regressions in the provider structure.

packages/tiptap/package.json (3)

20-20: Added workspace dependency for utils package

The addition of @hypr/utils as a workspace dependency provides access to shared utilities needed for the refactored transcript editing flow.


24-24: Added React Router dependency

Adding @tanstack/react-router supports improved navigation capabilities in the editor components. This is a modern, type-safe router implementation appropriate for the project.


26-26: Added Tiptap bubble menu extension

The @tiptap/extension-bubble-menu dependency enables the creation of context-sensitive floating menus in the editor, which is essential for the modular speaker node/view system mentioned in the PR summary.

apps/desktop/src/locales/ko/messages.po (3)

314-316: Updated source reference for localization entry

The line number reference for "Add participant" has been updated from line 341 to 311 to reflect changes in the participants-chip.tsx component structure.


471-473: Updated source reference for localization entry

The line number reference for "Create" has been updated from line 429 to 399 to reflect changes in the participants-chip.tsx component structure.


872-875: Removed obsolete localization entry

The "Remove {0} from list" string has been marked as obsolete, indicating this tooltip is no longer used in the refactored participants component. This aligns with the UI changes mentioned in the PR summary.

apps/desktop/src/locales/en/messages.po (3)

314-316: Updated source reference for localization entry

The line number reference for "Add participant" has been updated from line 341 to 311 to reflect the refactored component structure in participants-chip.tsx.


471-473: Updated source reference for localization entry

The line number reference for "Create" has been updated from line 429 to 399 to reflect the refactored component structure in participants-chip.tsx.


872-875: Removed obsolete localization entry

The "Remove {0} from list" localization entry has been properly marked as obsolete (commented out), ensuring consistency with the Korean localization and reflecting the UI changes in the participant management component.

packages/tiptap/src/transcript/utils.test.ts (1)

21-51: Improved test with detailed structure validation

The test has been enhanced to validate the exact structure of the editor content after converting from words. This additional assertion is valuable as it ensures the intermediate representation matches the expected format with all required attributes and nesting.

This change aligns with the broader refactoring of the transcript utility functions and supports the new speaker node model.

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

40-40: LGTM: Line height adjustment for transcript words

Setting a consistent line height for transcript words improves text alignment.


52-52: LGTM: Adjusted hover background color

The background color change for hover state provides better visual feedback.


56-60: LGTM: Improved non-editable word styling

These changes appropriately disable interactive styling in non-editable contexts by removing hover effects and setting the cursor to default, which provides better UX by clearly indicating when words aren't interactive.

packages/ui/src/components/ui/popover.tsx (2)

8-18: Enhanced PopoverTrigger with ref forwarding and consistent focus styles

Refactoring PopoverTrigger to use forwardRef properly passes DOM refs to the underlying component and applies consistent outline styles. This change improves component reusability and accessibility.


71-71: LGTM: Simplified focus styling for PopoverContent

Removing the focus-visible ring in favor of consistent outline-none styling aligns with the trigger's styling approach.

apps/desktop/src/components/right-panel/views/exp.tsx (2)

1-49: Well-implemented SpeakerSelector component with clean separation of concerns

This new component effectively handles speaker selection within a note session. It properly:

  • Extracts session ID from the URL using React Router
  • Fetches participants using React Query with appropriate query key and enabling condition
  • Maintains local state for popover visibility
  • Provides fallback display names when speaker data is unavailable
  • Uses the shared ParticipantsChipInner component for consistent UI

The component aligns well with the refactored transcript editing flow and modular speaker node/view system mentioned in the PR summary.


20-24:

Details

✅ Verification successful

Verify proper query invalidation for participants data

Ensure there's appropriate query invalidation logic elsewhere in the codebase when participants are added, removed, or modified, to keep this component's data in sync.


🏁 Script executed:

#!/bin/bash
# Check for query invalidation of participants data
echo "Searching for participant query invalidation..."
rg -A 3 "invalidateQueries.*participants" --glob "*.{ts,tsx}"

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Broad search for any query invalidations in the codebase
echo "Searching for all invalidateQueries usage across .ts/.tsx files..."
rg -A2 "invalidateQueries" --glob "*.{ts,tsx}"

Length of output: 4927


Participants query invalidation is implemented
The participants query is invalidated in the chip component whenever participants change, so no further invalidation hooks are required at this time.

• apps/desktop/src/components/editor-area/note-header/chips/participants-chip.tsx – calls
queryClient.invalidateQueries({ predicate: (query) =>
(query.queryKey[0] as string).includes("participants") && query.queryKey[1] === sessionId
})

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

19-24: Use a more descriptive prop name than c

c does not convey intent. A future reader has to navigate the code to learn that it is the speaker view component. Consider something like speakerView or renderSpeaker to improve self-documentation of the public API.

-interface TranscriptEditorProps {
-  editable?: boolean;
-  onBlur?: (words: Word[]) => void;
-  c: SpeakerViewInnerComponent;
-}
+interface TranscriptEditorProps {
+  editable?: boolean;
+  onBlur?: (words: Word[]) => void;
+  speakerView: SpeakerViewInnerComponent;
+}

[ suggest_nitpick ]


25-29: Expose the imperative API with useImperativeHandle

Directly mutating ref.current inside an effect bypasses React’s recommended pattern and can lead to stale closures when the object is recreated. useImperativeHandle guarantees the reference is kept in sync without extra effects.

-const TranscriptEditor = forwardRef<TranscriptEditorRef, TranscriptEditorProps>(
-  ({ editable = true, c, onBlur }, ref) => {
+const TranscriptEditor = forwardRef<TranscriptEditorRef, TranscriptEditorProps>(
+  ({ editable = true, speakerView, onBlur }, ref) => {-  const editor = useEditor({ … });
+  const editor = useEditor({ … });
+
+  useImperativeHandle(ref, () => ({
+    editor,
+    getWords: () => (editor ? fromEditorToWords(editor.getJSON() as any) : null),
+    setWords: (words: Word[]) => {
+      if (editor) {
+        editor.commands.setContent(fromWordsToEditor(words));
+      }
+    },
+  }), [editor]);

[ suggest_essential_refactor ]


68-74: Avoid resetting the entire document when words haven’t changed

setWords currently calls editor.commands.setContent unconditionally.
If the caller feeds the same words repeatedly (as happens in TranscriptView whenever the query refetches), the cursor position, selection, and undo history are lost each time.

Consider short-circuiting when the incoming words serialise to the same JSON as the current document:

 const setWords = (words: Word[]) => {
   if (!editor) return;
-  const content = fromWordsToEditor(words);
-  editor.commands.setContent(content);
+  const content = fromWordsToEditor(words);
+  if (JSON.stringify(content) !== JSON.stringify(editor.getJSON())) {
+    editor.commands.setContent(content, false); // preserve history flag
+  }
 };

[ suggest_optional_refactor ]


51-56: Verify that the onBlur/setWords round-trip cannot create an infinite update loop

onBlur ⇒ database update ⇒ react-query refetch ⇒ parent words update ⇒ setWordseditor.commands.setContent
If the DB returns the same words array, the optimisation above will mitigate churn; if it returns a different ordering or attribute defaults, the editor will keep losing focus selection.

Please confirm this flow or add debouncing / diffing at the persistence layer.

[ request_verification ]

packages/tiptap/src/transcript/views.tsx (2)

7-14: Attribute typing mismatch for speaker-index

node.attrs?.["speaker-index"] is a string (because getAttribute returns one), yet the prop is typed as number | undefined. Consumers relying on a numeric index may break.

-const speakerIndex = node.attrs?.["speaker-index"] ?? undefined;
+const rawIndex = node.attrs?.["speaker-index"];
+const speakerIndex = rawIndex !== undefined ? Number(rawIndex) : undefined;

Consider performing the same coercion when reading "speaker-id" if you expect numeric IDs later.

[ raise_critical_issue ]


31-38: Provide a way to update the speaker label alongside the ID

onSpeakerIdChange mutates only "speaker-id". If the label is stored on the node (for tooltips / UX), it will remain stale after the user selects a different participant.

Add an optional speakerLabel parameter or derive the label in the parent callback and update both attributes:

updateAttributes({ "speaker-id": newId, "speaker-label": newLabel });

[ suggest_optional_refactor ]

apps/desktop/src/components/right-panel/views/transcript-view.tsx (3)

30-37: Guard against editorRef.current being null on first render

words is often populated by a query before the editor ref is ready.
The existing if (editorRef.current) { … } guard covers this, but when the ref eventually becomes non-null, the latest words value may already be stale (missed by the dependency array).

Add editorRef.current to the dependency list or, better, move the initial content load to the editor’s onCreate callback exposed via TranscriptEditor.

[ request_verification ]


61-67: Handle DB failures when persisting on blur

handleBlur silently ignores rejection from upsertSession.
A network or filesystem error will therefore drop user edits without feedback.

-  dbCommands.getSession({ id: sessionId! }).then((session) => {
-    if (session) {
-      dbCommands.upsertSession({ ...session, words });
-    }
-  });
+  dbCommands.getSession({ id: sessionId! })
+    .then((session) => session && dbCommands.upsertSession({ ...session, words }))
+    .catch(console.error); // or report to the user / telemetry

[ suggest_optional_refactor ]


125-136: Potential UX issue: editor content resets on every words change

Because words is updated frequently (e.g. live transcription), setWords in the previous useEffect will overwrite any transient user selection or undo stack on every tick.

After applying the setWords short-circuit optimisation in TranscriptEditor this will improve, but you may still want to pause updates while the user is actively editing (e.g. track focus).

[ offer_architecture_advice ]

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

145-147: Query-key shape assumption may break future refactors.

predicate: (query) => (query.queryKey[0] as string).includes("participants") && query.queryKey[1] === sessionId
relies on:

  1. queryKey[0] being a string.
  2. The string containing "participants".

If queryKey becomes an object/tuple this filter starts to silently fail.
Safer: use an explicit tag in the key (['participants', { sessionId }]) or partial matcher (Array.isArray(k) && k[0] === 'participants').

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

♻️ Duplicate comments (2)
packages/tiptap/src/transcript/nodes.ts (2)

53-60: speaker-index comparison is type-unsafe and should be updated

The comparison between node.attrs["speaker-index"] and speakerIndex is type-unsafe. While line 183 correctly parses speaker-index as a number, here you're doing a direct comparison without conversion, which can fail if they're of different types.

Also, setting "speaker-index": null will still render the attribute in the DOM (line 185), potentially as the string "null".

Apply this fix to address both issues:

-if (node.type.name === "speaker" && node.attrs["speaker-index"] === speakerIndex) {
+const indexAttr = node.attrs["speaker-index"];
+if (
+  node.type.name === "speaker" &&
+  indexAttr != null &&
+  Number(indexAttr) === speakerIndex
+) {
   tr.setNodeMarkup(pos, undefined, {
     ...node.attrs,
-    "speaker-index": null,
+    "speaker-index": undefined,
     "speaker-id": speakerId,
     "speaker-label": speakerLabel,
   });

179-186: speaker-index attribute rendering should be consistent with other attributes

The renderHTML function for speaker-index doesn't handle the null case properly, which will cause "null" to appear in the DOM. Other attributes like start_ms in WordNode (line 242) properly omit the attribute when it's null/undefined.

Apply this fix:

"speaker-index": {
  default: null,
  parseHTML: element => {
    const v = element.getAttribute("data-speaker-index");
    return v !== null ? Number(v) : null;
  },
-  renderHTML: attributes => ({ "data-speaker-index": attributes["speaker-index"] }),
+  renderHTML: attributes => 
+    attributes["speaker-index"] != null
+      ? { "data-speaker-index": attributes["speaker-index"] }
+      : {},
},
🧹 Nitpick comments (2)
packages/tiptap/src/transcript/nodes.ts (2)

187-196: Apply consistent attribute handling for all speaker attributes

For consistency with speaker-index and WordNode attributes, the speaker-id and speaker-label attributes should also conditionally render and properly handle null values.

"speaker-id": {
  default: null,
  parseHTML: element => element.getAttribute("data-speaker-id"),
-  renderHTML: attributes => ({ "data-speaker-id": attributes["speaker-id"] }),
+  renderHTML: attributes => 
+    attributes["speaker-id"] != null
+      ? { "data-speaker-id": attributes["speaker-id"] }
+      : {},
},
"speaker-label": {
  default: null,
  parseHTML: element => element.getAttribute("data-speaker-label"),
-  renderHTML: attributes => ({ "data-speaker-label": attributes["speaker-label"] }),
+  renderHTML: attributes => 
+    attributes["speaker-label"] != null
+      ? { "data-speaker-label": attributes["speaker-label"] }
+      : {},
},

223-223: Avoid using any type casting

Using as any bypasses TypeScript's type checking, which can lead to potential runtime errors. Consider using a more specific type or implementing the interface correctly.

-return implementCommands as any; // casting because util object is compatible
+// Define a type that represents the structure of the commands
+type SpeakerCommands = {
+  [K in keyof Commands<any>['speaker']]: Commands<any>['speaker'][K]
+};
+return implementCommands as SpeakerCommands;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 098f06e and ac93027.

📒 Files selected for processing (1)
  • packages/tiptap/src/transcript/nodes.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: ci (windows, windows-latest)
🔇 Additional comments (3)
packages/tiptap/src/transcript/nodes.ts (3)

172-226: Well-designed factory pattern for SpeakerNode

The factory pattern implemented for SpeakerNode is a great approach that allows for component injection and flexibility. This makes the node more reusable and testable while separating concerns between data and presentation.


8-44: Good use of TypeScript module augmentation

The module augmentation for @tiptap/core is a clean way to extend the Commands interface with speaker-specific commands. This ensures proper type checking and autocompletion for consumers of your API.


46-170: Command implementations provide a comprehensive API

The command implementations provide a rich API for manipulating speaker nodes in different contexts. The structure with explicit checks for dispatch and returning appropriate boolean values follows Tiptap's command pattern correctly.

A minor suggestion to improve code consistency:

  • Consider extracting the common logic for checking and updating nodes into helper functions
  • Standardize the return flow pattern across all commands

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 (3)
apps/desktop/src/components/right-panel/views/transcript-view.tsx (3)

138-138: Use a more descriptive prop name.

The prop name c is not descriptive of what it represents. Consider using a more meaningful name like speakerComponent or speakerSelector to improve code readability.

-                c={SpeakerSelector}
+                speakerComponent={SpeakerSelector}

197-242: Well-implemented speaker selection component with minor accessibility improvement.

The SpeakerSelector component provides a clean interface for changing the speaker associated with transcript segments. It correctly handles different states and integrates well with the ParticipantsChipInner component.

Add an aria-label to improve accessibility:

-        <PopoverTrigger>
+        <PopoverTrigger aria-label={`Change speaker: ${displayName}`}>
           <span className="underline py-1 font-semibold">{displayName}</span>
         </PopoverTrigger>

220-220: Consider externalizing the speaker naming convention.

The fallback naming convention for speakers (Speaker ${speakerIndex}) is hardcoded. Consider extracting this to a constant or utility function for consistent naming across the application.

- const displayName = foundSpeaker?.full_name ?? `Speaker ${speakerIndex}`;
+ const displayName = foundSpeaker?.full_name ?? getDefaultSpeakerName(speakerIndex);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac93027 and c65375c.

📒 Files selected for processing (3)
  • apps/desktop/src/components/right-panel/views/transcript-view.tsx (7 hunks)
  • crates/db-user/src/init.rs (1 hunks)
  • packages/tiptap/src/transcript/index.tsx (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • crates/db-user/src/init.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/tiptap/src/transcript/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: ci (windows, windows-latest)
  • GitHub Check: ci
  • GitHub Check: ci (macos, macos-latest)
🔇 Additional comments (3)
apps/desktop/src/components/right-panel/views/transcript-view.tsx (3)

1-16: Well-organized imports with proper type usage.

The import statements are well-organized with clear separation between external and internal dependencies. The explicit import of types like TranscriptEditorRef is good practice for maintaining type safety.


35-39: Good implementation of editor synchronization.

The useEffect hook correctly synchronizes the words state with the editor when the words prop changes. This ensures the editor always reflects the latest data without causing unnecessary re-renders.


129-141: Clean implementation of the transcript editor integration.

The code cleanly integrates the TranscriptEditor component and handles the empty state appropriately. The editor is only editable when the session is inactive, which is a good constraint to prevent editing during recording.

@yujonglee yujonglee merged commit c88ee45 into main May 22, 2025
6 checks passed
@yujonglee yujonglee deleted the transcript-editor-2 branch May 22, 2025 16:18
@coderabbitai coderabbitai bot mentioned this pull request Nov 2, 2025
@coderabbitai coderabbitai bot mentioned this pull request Nov 12, 2025
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

Comments