Skip to content

Conversation

@ComputelessComputer
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Nov 15, 2025

📝 Walkthrough

Walkthrough

This PR makes widespread UI and styling adjustments across the desktop app: updates chat and main body component styling (borders, padding, spacing), refactors the participants component from a list-based UI to a chip-centric add/remove flow, overhauls template settings with new data-fetching hooks, adds draggable positioning to the devtool trigger, and updates global typography with Inter and Lora fonts replacing Racing Sans One.

Changes

Cohort / File(s) Summary
Chat component styling
apps/desktop/src/components/chat/body/index.tsx, apps/desktop/src/components/chat/header.tsx, apps/desktop/src/components/chat/session.tsx, apps/desktop/src/components/chat/view.tsx
Border color utilities, vertical padding removal, flex layout adjustments, gap spacing for vertical alignment between children.
Main body and layout styling
apps/desktop/src/components/main/body/index.tsx, apps/desktop/src/components/main/body/search.tsx, apps/desktop/src/components/main/body/sessions/index.tsx
Border color updates, placeholder text styling, shrink utility consolidation.
Participants chip refactor
apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx
Replaced ParticipantItem and ParticipantCandidates with ParticipantChip and ParticipantChipInput; introduced compact chip-based UI with inline adding, keyboard navigation, and removal actions.
Settings general styling
apps/desktop/src/components/settings/general/app-settings.tsx, apps/desktop/src/components/settings/general/permissions.tsx
Adjusted vertical spacing, changed alignment from top to center, replaced spinner UI with icon-only indicators (CheckIcon/ArrowRightIcon).
Settings notification refactor
apps/desktop/src/components/settings/notification.tsx
Added input-driven dropdown for ignored apps with filtering, custom-add option, keyboard navigation, chip-based Badge list for ignored apps, and type-safe query handling.
Template settings refactor
apps/desktop/src/components/settings/template/index.tsx, apps/desktop/src/components/settings/template/search.tsx, apps/desktop/src/components/settings/template/sections.tsx, apps/desktop/src/components/settings/template/editor.tsx, apps/desktop/src/components/settings/template/shared.tsx
Unified user and suggested templates with new data-fetching hooks (useUserTemplates, useSuggestedTemplates), replaced form-based search with simple input, restructured sections layout, added "Made by me" badge, removed shadow from textarea.
Settings sidebar banner
apps/desktop/src/components/main/sidebar/banner/registry.tsx
Updated banner titles from "Configure STT model" to "Missing AI model" and "Configure LLM" to "Add intelligence".
Devtool draggable trigger
apps/desktop/src/devtool/index.tsx
Added dragging state management, mouse/touch handlers, viewport bounds constraint, position prop and onPositionChange callback to DevtoolTrigger; updated drawer width from w-[240px] to w-60.
Settings layout and routing
apps/desktop/src/routes/app/settings/_layout.tsx
Replaced DropdownMenu-based delete action with direct Trash2 icon button.
Global styles and configuration
apps/desktop/styles/globals.css, apps/desktop/tailwind.config.ts
Changed font imports from Racing Sans One to Inter (400-700) and Lora (400-700); added text-wrap: balance to headings; updated Tailwind fontFamily config to use "serif" and "sans" keys.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ParticipantChipInput
    participant Dropdown
    participant Store

    User->>ParticipantChipInput: Type to search/add participant
    ParticipantChipInput->>ParticipantChipInput: Filter existing + default-ignored apps
    ParticipantChipInput->>Dropdown: Show filtered suggestions + custom-add option
    User->>Dropdown: Select existing or create new
    alt Existing participant
        Dropdown->>Store: Link/map participant
    else New participant
        Dropdown->>Store: Create new participant
    end
    Store->>ParticipantChipInput: Update mapping
    ParticipantChipInput->>ParticipantChipInput: Render ParticipantChip with dismiss action
    User->>ParticipantChip: Click X to remove
    ParticipantChip->>Store: Remove mapping
Loading
sequenceDiagram
    participant User
    participant SettingsTemplates
    participant useUserTemplates
    participant useSuggestedTemplates
    participant TinyBase
    participant RemoteAPI

    User->>SettingsTemplates: Enter search query
    SettingsTemplates->>useUserTemplates: Fetch user templates
    useUserTemplates->>TinyBase: Query templates by user_id
    TinyBase-->>useUserTemplates: Return user templates
    SettingsTemplates->>useSuggestedTemplates: Fetch suggested templates
    useSuggestedTemplates->>RemoteAPI: GET suggested templates
    RemoteAPI-->>useSuggestedTemplates: Return remote templates
    useSuggestedTemplates->>useSuggestedTemplates: Filter by search query
    useSuggestedTemplates-->>SettingsTemplates: Return filtered suggestions
    SettingsTemplates->>SettingsTemplates: Render TemplateCard list or no-results panel
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Areas requiring extra attention:

  • Participants refactor (participants.tsx): Significant logic change from list-based to chip-based UI; verify ParticipantChip and ParticipantChipInput interact correctly with store, keyboard navigation, and removal flows.
  • Template settings refactor (template/index.tsx): New hooks (useUserTemplates, useSuggestedTemplates) introduce data-fetching patterns; validate query logic, filtering, empty states, and integration with TemplateCard.
  • Devtool dragging (devtool/index.tsx): New drag state management and viewport constraints; test boundary conditions, drag/click disambiguation, and position persistence.
  • Notification settings (notification.tsx): Input-driven dropdown with keyboard navigation, outside-click handling, and type-safe query results; verify all interaction paths and state transitions.
  • Settings layout (_layout.tsx): Confirm delete action simplification (removing dropdown) maintains intended behavior and confirmation flow.

Possibly related PRs

Suggested reviewers

  • yujonglee

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess relevance to the changeset. Add a pull request description explaining the purpose and scope of the UI changes, including details about font updates, spacing adjustments, and component refactoring.
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.
Title check ❓ Inconclusive The title 'global-ui-changes' is vague and generic, using a non-descriptive term that doesn't convey specific information about the primary changes. Replace with a more specific title describing the main changes, such as 'Refactor UI styling with new font stack and layout adjustments' or 'Update typography and spacing throughout desktop app'.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch global-ui-changes

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/components/settings/template/sections.tsx (1)

144-234: Drag handle no longer initiates reordering

useDragControls is instantiated once in SectionsList, but the corresponding Reorder.Item never receives those controls. Because the handle’s onPointerDown calls controls.start on an object that isn’t bound to the item, pressing the handle can’t initiate a drag, and only the default full-card drag listener keeps reordering usable. This regresses the dedicated handle UX and breaks when dragListener is disabled (which the new layout strongly implies).

Bind a fresh useDragControls to each Reorder.Item so the handle works reliably:

-          {drafts.map((draft) => (
-            <Reorder.Item key={draft.key} value={draft}>
-              <SectionItem
-                disabled={disabled}
-                item={draft}
-                onChange={changeSection}
-                onDelete={deleteSection}
-                dragControls={controls}
-              />
-            </Reorder.Item>
-          ))}
+          {drafts.map((draft) => (
+            <SectionItem
+              key={draft.key}
+              disabled={disabled}
+              item={draft}
+              onChange={changeSection}
+              onDelete={deleteSection}
+            />
+          ))}
-function SectionItem({
-  disabled,
-  item,
-  onChange,
-  onDelete,
-  dragControls,
-}: {
+function SectionItem({
+  disabled,
+  item,
+  onChange,
+  onDelete,
+}: {
   disabled: boolean;
   item: SectionDraft;
   onChange: (item: SectionDraft) => void;
   onDelete: (key: string) => void;
-  dragControls: ReturnType<typeof useDragControls>;
 }) {
   const [isFocused, setIsFocused] = useState(false);
+  const dragControls = useDragControls();
 
-  return (
-    <div className="group relative bg-white">
+  return (
+    <Reorder.Item
+      value={item}
+      dragControls={dragControls}
+      dragListener={false}
+    >
+      <div className="group relative bg-white">
@@
-      <button
+      <button
         className="absolute -left-5 top-2.5 cursor-move opacity-0 group-hover:opacity-30 hover:opacity-60 transition-opacity"
-        onPointerDown={(event) => dragControls.start(event)}
+        onPointerDown={(event) => dragControls.start(event)}
         disabled={disabled}
       >
         <HandleIcon className="h-4 w-4 text-muted-foreground" />
       </button>
@@
-    </div>
-  );
+      </div>
+    </Reorder.Item>
+  );
🧹 Nitpick comments (3)
apps/desktop/src/components/settings/notification.tsx (1)

163-184: Consider awaiting form submission to prevent race conditions.

The form.handleSubmit() calls on lines 174 and 183 are not awaited, which could lead to race conditions during rapid add/remove operations.

Apply this diff to await the submissions:

   form.setFieldValue("ignored_platforms", [...ignoredPlatforms, trimmedName]);
-  form.handleSubmit();
+  await form.handleSubmit();
   setInputValue("");
   setShowDropdown(false);
   setSelectedIndex(0);
 };

 const handleRemoveIgnoredApp = (app: string) => {
   const updated = ignoredPlatforms.filter((a: string) => a !== app);
   form.setFieldValue("ignored_platforms", updated);
-  form.handleSubmit();
+  await form.handleSubmit();
 };

Also update the function signatures to be async:

-const handleAddIgnoredApp = (appName: string) => {
+const handleAddIgnoredApp = async (appName: string) => {
   // ...
 };

-const handleRemoveIgnoredApp = (app: string) => {
+const handleRemoveIgnoredApp = async (app: string) => {
   // ...
 };
apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx (2)

113-165: Consider performance optimization for speaker hint cleanup.

The forEachRow iteration over all speaker_hints could become inefficient as the number of hints grows. If available, consider using an index to filter hints by session_id before iterating, rather than checking every hint in the system.


242-271: Optimize candidate filtering logic.

The filtering checks inputValue twice: once on line 257 and again in the search logic within the map. The filter condition if (inputValue && !nameMatch && !emailMatch) should be moved earlier to avoid unnecessary string operations.

Consider this refactor:

   const candidates = useMemo(() => {
     const searchLower = inputValue.toLowerCase();
+    const hasSearch = inputValue.trim().length > 0;
+    
     return allHumanIds
       .filter((humanId: string) => !existingHumanIds.has(humanId))
       .map((humanId: string) => {
         const human = store?.getRow("humans", humanId);
         if (!human) {
           return null;
         }

         const name = (human.name || "") as string;
         const email = (human.email || "") as string;
-        const nameMatch = name.toLowerCase().includes(searchLower);
-        const emailMatch = email.toLowerCase().includes(searchLower);

-        if (inputValue && !nameMatch && !emailMatch) {
+        if (hasSearch) {
+          const nameMatch = name.toLowerCase().includes(searchLower);
+          const emailMatch = email.toLowerCase().includes(searchLower);
+          if (!nameMatch && !emailMatch) {
-          return null;
+            return null;
+          }
         }

         return {
           id: humanId,
           name,
           email,
           orgId: human.org_id as string | undefined,
           jobTitle: human.job_title as string | undefined,
           isNew: false,
         };
       })
       .filter((h): h is NonNullable<typeof h> => h !== null);
   }, [inputValue, allHumanIds, existingHumanIds, store]);
📜 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 5c33b0a and 31457f9.

⛔ Files ignored due to path filters (3)
  • packages/ui/src/components/ui/button.tsx is excluded by !packages/ui/src/components/ui/**
  • packages/ui/src/components/ui/tooltip.tsx is excluded by !packages/ui/src/components/ui/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • apps/desktop/src/components/chat/body/index.tsx (1 hunks)
  • apps/desktop/src/components/chat/header.tsx (2 hunks)
  • apps/desktop/src/components/chat/session.tsx (1 hunks)
  • apps/desktop/src/components/chat/view.tsx (1 hunks)
  • apps/desktop/src/components/main/body/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/search.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/index.tsx (1 hunks)
  • apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx (8 hunks)
  • apps/desktop/src/components/main/sidebar/banner/registry.tsx (2 hunks)
  • apps/desktop/src/components/settings/general/app-settings.tsx (2 hunks)
  • apps/desktop/src/components/settings/general/permissions.tsx (3 hunks)
  • apps/desktop/src/components/settings/notification.tsx (5 hunks)
  • apps/desktop/src/components/settings/template/editor.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/index.tsx (2 hunks)
  • apps/desktop/src/components/settings/template/search.tsx (1 hunks)
  • apps/desktop/src/components/settings/template/sections.tsx (3 hunks)
  • apps/desktop/src/components/settings/template/shared.tsx (2 hunks)
  • apps/desktop/src/devtool/index.tsx (3 hunks)
  • apps/desktop/src/routes/app/settings/_layout.tsx (2 hunks)
  • apps/desktop/src/styles/globals.css (3 hunks)
  • apps/desktop/tailwind.config.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
apps/desktop/src/components/settings/template/index.tsx (5)
apps/desktop/src/components/settings/template/utils.ts (2)
  • useTemplateNavigation (6-105)
  • normalizeTemplateWithId (107-112)
apps/desktop/src/components/settings/template/search.tsx (1)
  • TemplateSearch (3-22)
apps/desktop/src/components/settings/template/shared.tsx (1)
  • TemplateCard (3-74)
apps/desktop/src/store/tinybase/schema-external.ts (1)
  • Template (153-153)
packages/db/src/schema.ts (1)
  • templates (253-264)
apps/desktop/src/components/settings/general/permissions.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/notification.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/devtool/index.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
apps/desktop/src/components/settings/template/sections.tsx (1)
packages/utils/src/cn.ts (1)
  • cn (20-22)
⏰ 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: fmt
🔇 Additional comments (27)
apps/desktop/src/routes/app/settings/_layout.tsx (1)

17-17: LGTM!

The Trash2 icon import is appropriate for the new delete button implementation.

apps/desktop/src/components/settings/notification.tsx (7)

1-25: LGTM! Clean setup for the new dropdown UI.

The imports and state initialization are well-structured to support the input-driven dropdown with keyboard navigation and outside-click handling.


42-62: LGTM! Enhanced type safety.

The explicit Result type annotations on the select callbacks improve type safety and code clarity.


146-161: LGTM! Well-structured filtering logic.

The filtering correctly excludes already-added and default-ignored apps while supporting custom app additions through the input field. The case-insensitive search provides good UX.


186-214: LGTM! Comprehensive keyboard navigation.

The keyboard handling covers all expected interactions: Enter to select, Arrow keys for navigation, Escape to close, and Backspace to remove the last chip. The logic correctly prevents removing default-ignored apps.


216-220: LGTM! Clean input handler.

The handler correctly updates the input value, shows the dropdown, and resets the selection index.


223-235: LGTM! Standard outside-click pattern.

The effect correctly closes the dropdown when clicking outside the container, with proper cleanup.


238-431: LGTM! Well-structured dropdown UI with chips.

The UI successfully replaces the previous popover with an intuitive input-driven dropdown:

  • Badge chips clearly display ignored apps with default indicators
  • Inline input provides seamless adding experience
  • Dropdown positioning and scrolling are properly handled
  • Conditional visibility based on microphone detection state
  • Clear visual hierarchy with the "For enabled notifications" divider

The component maintains accessibility with keyboard navigation and properly manages focus states.

apps/desktop/src/styles/globals.css (3)

1-1: LGTM! Font imports align with Tailwind configuration.

The Google Fonts import correctly includes Inter and Lora with the specified weights, and the display=swap parameter ensures good font loading performance.


89-91: LGTM! Improved readability.

The multi-line formatting of the selectors improves code readability without any functional changes.


57-64: Confirm browser support requirements for text-wrap: balance.

The text-wrap: balance property is now widely supported: Chrome 114+, Firefox 121+, Safari 17.5+, and current mobile browsers. The original concern about limited support was outdated.

However, verify your desktop app's browser support requirements. If you target older browsers (pre-Chrome 114, pre-Firefox 121, or pre-Safari 17.5), this property will have no effect on those versions, potentially causing inconsistent heading layouts. Additionally, browsers impose performance limits for line balancing, so test heading behavior if you have very long titles.

apps/desktop/tailwind.config.ts (1)

7-10: No issues found—breaking change has been properly addressed.

Verification confirms no usages of font-racing-sans exist anywhere in the codebase. The removal of the racing-sans font family utility and its replacement with serif and sans has been properly handled across all components.

apps/desktop/src/components/chat/view.tsx (1)

112-112: LGTM - Spacing enhancement.

The addition of gap-1 provides consistent vertical spacing between the chat header and session components.

apps/desktop/src/components/main/body/sessions/index.tsx (1)

107-107: LGTM - Tailwind shorthand update.

The change from flex-shrink-0 to shrink-0 is a modern Tailwind syntax preference with identical behavior.

apps/desktop/src/components/main/sidebar/banner/registry.tsx (1)

33-33: LGTM - Improved user-facing copy.

The updated banner titles ("Missing AI model" and "Add intelligence") are more approachable and clearer for end users compared to the technical terms "Configure STT model" and "Configure LLM".

Also applies to: 48-48

apps/desktop/src/components/settings/general/app-settings.tsx (2)

26-26: LGTM - Spacing adjustment.

Reducing vertical spacing from space-y-6 to space-y-4 creates a more compact, visually balanced settings layout.


68-68: LGTM - Improved vertical alignment.

Changing from items-start to items-center better aligns the Switch control with the setting's text content.

apps/desktop/src/components/chat/body/index.tsx (1)

41-41: LGTM - Explicit border color.

Adding border-neutral-200 ensures consistent border styling rather than relying on browser defaults. This improves visual consistency across the application.

apps/desktop/src/components/chat/session.tsx (1)

156-158: LGTM - Layout wrapper addition.

Wrapping children in a flex container establishes explicit layout constraints (flex-1 h-full flex flex-col), ensuring consistent sizing and layout behavior for child components.

apps/desktop/src/components/chat/header.tsx (2)

39-39: LGTM - Simplified padding.

Removing explicit vertical padding (py-0.5) while maintaining fixed height (h-9) simplifies the styling without affecting the visual result.


200-200: LGTM - Tailwind shorthand update.

The change from flex-shrink-0 to shrink-0 is a modern Tailwind syntax preference with identical behavior.

apps/desktop/src/components/settings/general/permissions.tsx (4)

32-32: LGTM - Improved vertical alignment.

Changing from items-start to items-center creates better visual balance between the permission description and the action button.


54-58: LGTM - Accessibility enhancement.

The addition of descriptive aria-label attributes improves screen reader support by clearly indicating the permission state and available actions.


60-64: LGTM - Icon-based UI simplification.

The icon-only approach (CheckIcon for granted, ArrowRightIcon for actionable) provides clear visual feedback while maintaining a compact design.


47-64: Verify loading state visibility.

The button is now disabled during isPending state (Line 49), but there's no visual loading indicator since the Spinner was removed. Users may not realize a permission request is in progress.

Consider adding a visual loading state, such as:

  • An animated icon
  • A pulsing effect on the button
  • Re-introducing a small spinner within the icon button

This ensures users have clear feedback when permission requests are processing, especially if the system dialog takes time to appear or the request is delayed.

apps/desktop/src/components/main/body/index.tsx (1)

339-339: LGTM! Border color standardization.

The addition of explicit border-neutral-200 improves visual consistency across the UI components, aligning with the broader styling updates in this PR.

apps/desktop/src/components/main/body/search.tsx (1)

143-143: LGTM! Enhanced placeholder styling.

The addition of placeholder-specific classes improves the visual consistency of the search input, ensuring placeholder text has appropriate size and color styling.

Comment on lines +191 to +202
<Button
type="button"
variant="ghost"
size="sm"
className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent"
onClick={(e) => {
e.stopPropagation();
handleRemove();
}}
>
<X className="h-2.5 w-2.5" />
</Button>
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

Add aria-label to the remove button for accessibility.

The remove button lacks an accessible label, making it unclear to screen reader users what the button does.

Apply this diff:

       <Button
         type="button"
         variant="ghost"
         size="sm"
         className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent"
+        aria-label={`Remove ${humanName || "participant"}`}
         onClick={(e) => {
           e.stopPropagation();
           handleRemove();
         }}
       >
         <X className="h-2.5 w-2.5" />
       </Button>
📝 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
<Button
type="button"
variant="ghost"
size="sm"
className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent"
onClick={(e) => {
e.stopPropagation();
handleRemove();
}}
>
<X className="h-2.5 w-2.5" />
</Button>
<Button
type="button"
variant="ghost"
size="sm"
className="ml-0.5 h-3 w-3 p-0 hover:bg-transparent"
aria-label={`Remove ${humanName || "participant"}`}
onClick={(e) => {
e.stopPropagation();
handleRemove();
}}
>
<X className="h-2.5 w-2.5" />
</Button>
🤖 Prompt for AI Agents
In
apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx
around lines 191 to 202, the remove Button has no accessible label; add an
aria-label attribute (e.g., aria-label="Remove participant" or
aria-label="Remove") to the Button element so screen readers announce its
purpose, keeping the existing onClick and styling unchanged.

Comment on lines +334 to +338
} else if (e.key === "Backspace" && !inputValue && mappingIds.length > 0) {
const lastMappingId = mappingIds[mappingIds.length - 1];
if (store) {
store.delRow("mapping_session_participant", lastMappingId);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add confirmation for Backspace deletion of last participant.

Pressing Backspace when the input is empty immediately deletes the last participant without confirmation. This could lead to accidental deletions, especially when users are typing quickly.

Consider adding a confirmation step or requiring two consecutive Backspace presses to delete, similar to how some tag input components work.

Comment on lines +382 to +414
{showDropdown && inputValue.trim() && dropdownOptions.length > 0 && (
<div className="absolute z-50 w-full mt-1 bg-popover border rounded-md shadow-md overflow-hidden">
<div className="max-h-[200px] overflow-auto py-1">
{dropdownOptions.map((option, index) => (
<button
key={option.id}
type="button"
className={cn([
"w-full px-3 py-1.5 text-left text-sm transition-colors",
"hover:bg-accent hover:text-accent-foreground",
selectedIndex === index && "bg-accent text-accent-foreground",
])}
onClick={() => handleAddParticipant(option)}
onMouseEnter={() => setSelectedIndex(index)}
>
{option.isNew ? (
<span>
Add "<span className="font-medium">{option.name}</span>"
</span>
) : (
<span className="flex items-center gap-2">
<span className="font-medium">{option.name}</span>
{option.jobTitle && (
<span className="text-xs text-muted-foreground">
{option.jobTitle}
</span>
)}
</span>
)}
</button>
))}
</div>
</div>
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

Add ARIA attributes to the dropdown for accessibility.

The dropdown lacks proper ARIA attributes for screen reader accessibility. The relationship between the input and the dropdown should be explicitly declared.

Apply these changes:

         <input
           ref={inputRef}
           type="text"
+          role="combobox"
+          aria-expanded={showDropdown && inputValue.trim() && dropdownOptions.length > 0}
+          aria-controls="participant-dropdown"
+          aria-activedescendant={showDropdown && dropdownOptions.length > 0 ? `participant-option-${selectedIndex}` : undefined}
           className="flex-1 min-w-[120px] bg-transparent outline-none text-sm placeholder:text-neutral-400"
           placeholder={mappingIds.length === 0 ? "Add participants..." : ""}
           value={inputValue}
           onChange={(e) => handleInputChange(e.target.value)}
           onKeyDown={handleKeyDown}
           onFocus={() => setShowDropdown(true)}
         />
       </div>
       {showDropdown && inputValue.trim() && dropdownOptions.length > 0 && (
-        <div className="absolute z-50 w-full mt-1 bg-popover border rounded-md shadow-md overflow-hidden">
-          <div className="max-h-[200px] overflow-auto py-1">
+        <div 
+          id="participant-dropdown"
+          role="listbox"
+          className="absolute z-50 w-full mt-1 bg-popover border rounded-md shadow-md overflow-hidden"
+        >
+          <div className="max-h-[200px] overflow-auto py-1">
             {dropdownOptions.map((option, index) => (
               <button
+                id={`participant-option-${index}`}
+                role="option"
+                aria-selected={selectedIndex === index}
                 key={option.id}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +28 to +83
const hasNoResults =
userTemplates.length === 0 && suggestedTemplates.length === 0;

return (
<div className="flex flex-col gap-8">
<TemplateSearch value={searchQuery} onChange={setSearchQuery} />
<LocalTemplates query={searchQuery} />
<RemoteTemplates query={searchQuery} />
<div className="space-y-6">
<div>
<p className="text-xs text-neutral-600 mb-4">
Create templates to structure and standardize your meeting notes
</p>
<div className="rounded-xl border border-neutral-200 bg-stone-50 mb-6">
<TemplateSearch value={searchQuery} onChange={setSearchQuery} />
</div>

{hasNoResults ? (
<div className="text-center py-12 text-neutral-500 bg-neutral-50 rounded-lg p-4 border border-neutral-200">
<BookText size={48} className="mx-auto mb-4 text-neutral-300" />
<p className="text-sm">
{searchQuery.length > 0
? "No templates found"
: "No templates yet"}
</p>
<p className="text-xs text-neutral-400 mt-1">
{searchQuery.length > 0
? "Try a different search term"
: "Create a template to get started."}
</p>
</div>
) : (
<div className="space-y-4">
{userTemplates.map((template) => (
<TemplateCard
key={template.id}
title={template.title}
description={template.description}
category="mine"
targets={template.targets}
onClick={() => goToEdit(template.id)}
/>
))}
{suggestedTemplates.map((template, index) => (
<TemplateCard
key={`suggested-${index}`}
title={template.title}
description={template.description}
category={template.category}
targets={template.targets}
onClick={() =>
cloneAndEdit({
title: template.title,
description: template.description,
sections: template.sections,
})
}
/>
))}
</div>
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

Search ignores personal templates

searchQuery only feeds the suggested templates path. userTemplates stays unfiltered, so typing a query still shows every personal template and hasNoResults never becomes true while any template exists. That’s a functional regression for the search UI.

Filter userTemplates alongside the remote suggestions and base hasNoResults on the filtered arrays:

-  const hasNoResults =
-    userTemplates.length === 0 && suggestedTemplates.length === 0;
+  const filteredUserTemplates = useMemo(() => {
+    if (!searchQuery.trim()) {
+      return userTemplates;
+    }
+
+    const lowerQuery = searchQuery.toLowerCase();
+
+    return userTemplates.filter((template) => {
+      const titleMatch = template.title
+        ?.toLowerCase()
+        .includes(lowerQuery);
+      const descriptionMatch = template.description
+        ?.toLowerCase()
+        .includes(lowerQuery);
+      const categoryMatch = template.category
+        ?.toLowerCase()
+        .includes(lowerQuery);
+      const targetsMatch = template.targets?.some((target) =>
+        target?.toLowerCase().includes(lowerQuery),
+      );
+
+      return (
+        titleMatch ||
+        descriptionMatch ||
+        categoryMatch ||
+        targetsMatch
+      );
+    });
+  }, [searchQuery, userTemplates]);
+
+  const hasNoResults =
+    filteredUserTemplates.length === 0 && suggestedTemplates.length === 0;
@@
-            {userTemplates.map((template) => (
+            {filteredUserTemplates.map((template) => (
📝 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
const hasNoResults =
userTemplates.length === 0 && suggestedTemplates.length === 0;
return (
<div className="flex flex-col gap-8">
<TemplateSearch value={searchQuery} onChange={setSearchQuery} />
<LocalTemplates query={searchQuery} />
<RemoteTemplates query={searchQuery} />
<div className="space-y-6">
<div>
<p className="text-xs text-neutral-600 mb-4">
Create templates to structure and standardize your meeting notes
</p>
<div className="rounded-xl border border-neutral-200 bg-stone-50 mb-6">
<TemplateSearch value={searchQuery} onChange={setSearchQuery} />
</div>
{hasNoResults ? (
<div className="text-center py-12 text-neutral-500 bg-neutral-50 rounded-lg p-4 border border-neutral-200">
<BookText size={48} className="mx-auto mb-4 text-neutral-300" />
<p className="text-sm">
{searchQuery.length > 0
? "No templates found"
: "No templates yet"}
</p>
<p className="text-xs text-neutral-400 mt-1">
{searchQuery.length > 0
? "Try a different search term"
: "Create a template to get started."}
</p>
</div>
) : (
<div className="space-y-4">
{userTemplates.map((template) => (
<TemplateCard
key={template.id}
title={template.title}
description={template.description}
category="mine"
targets={template.targets}
onClick={() => goToEdit(template.id)}
/>
))}
{suggestedTemplates.map((template, index) => (
<TemplateCard
key={`suggested-${index}`}
title={template.title}
description={template.description}
category={template.category}
targets={template.targets}
onClick={() =>
cloneAndEdit({
title: template.title,
description: template.description,
sections: template.sections,
})
}
/>
))}
</div>
const filteredUserTemplates = useMemo(() => {
if (!searchQuery.trim()) {
return userTemplates;
}
const lowerQuery = searchQuery.toLowerCase();
return userTemplates.filter((template) => {
const titleMatch = template.title
?.toLowerCase()
.includes(lowerQuery);
const descriptionMatch = template.description
?.toLowerCase()
.includes(lowerQuery);
const categoryMatch = template.category
?.toLowerCase()
.includes(lowerQuery);
const targetsMatch = template.targets?.some((target) =>
target?.toLowerCase().includes(lowerQuery),
);
return (
titleMatch ||
descriptionMatch ||
categoryMatch ||
targetsMatch
);
});
}, [searchQuery, userTemplates]);
const hasNoResults =
filteredUserTemplates.length === 0 && suggestedTemplates.length === 0;
return (
<div className="space-y-6">
<div>
<p className="text-xs text-neutral-600 mb-4">
Create templates to structure and standardize your meeting notes
</p>
<div className="rounded-xl border border-neutral-200 bg-stone-50 mb-6">
<TemplateSearch value={searchQuery} onChange={setSearchQuery} />
</div>
{hasNoResults ? (
<div className="text-center py-12 text-neutral-500 bg-neutral-50 rounded-lg p-4 border border-neutral-200">
<BookText size={48} className="mx-auto mb-4 text-neutral-300" />
<p className="text-sm">
{searchQuery.length > 0
? "No templates found"
: "No templates yet"}
</p>
<p className="text-xs text-neutral-400 mt-1">
{searchQuery.length > 0
? "Try a different search term"
: "Create a template to get started."}
</p>
</div>
) : (
<div className="space-y-4">
{filteredUserTemplates.map((template) => (
<TemplateCard
key={template.id}
title={template.title}
description={template.description}
category="mine"
targets={template.targets}
onClick={() => goToEdit(template.id)}
/>
))}
{suggestedTemplates.map((template, index) => (
<TemplateCard
key={`suggested-${index}`}
title={template.title}
description={template.description}
category={template.category}
targets={template.targets}
onClick={() =>
cloneAndEdit({
title: template.title,
description: template.description,
sections: template.sections,
})
}
/>
))}
</div>

@yujonglee yujonglee merged commit 8ab6845 into main Nov 16, 2025
21 checks passed
@yujonglee yujonglee deleted the global-ui-changes branch November 16, 2025 09:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants