-
Notifications
You must be signed in to change notification settings - Fork 414
global-ui-changes #1670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
global-ui-changes #1670
Conversation
📝 WalkthroughWalkthroughThis 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
useDragControlsis instantiated once inSectionsList, but the correspondingReorder.Itemnever receives those controls. Because the handle’sonPointerDowncallscontrols.starton 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 whendragListeneris disabled (which the new layout strongly implies).Bind a fresh
useDragControlsto eachReorder.Itemso 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
forEachRowiteration over allspeaker_hintscould become inefficient as the number of hints grows. If available, consider using an index to filter hints bysession_idbefore iterating, rather than checking every hint in the system.
242-271: Optimize candidate filtering logic.The filtering checks
inputValuetwice: once on line 257 and again in the search logic within the map. The filter conditionif (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
⛔ Files ignored due to path filters (3)
packages/ui/src/components/ui/button.tsxis excluded by!packages/ui/src/components/ui/**packages/ui/src/components/ui/tooltip.tsxis excluded by!packages/ui/src/components/ui/**pnpm-lock.yamlis 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
Trash2icon 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
Resulttype 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=swapparameter 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 fortext-wrap: balance.The
text-wrap: balanceproperty 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-sansexist anywhere in the codebase. The removal of theracing-sansfont family utility and its replacement withserifandsanshas been properly handled across all components.apps/desktop/src/components/chat/view.tsx (1)
112-112: LGTM - Spacing enhancement.The addition of
gap-1provides 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-0toshrink-0is 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-6tospace-y-4creates a more compact, visually balanced settings layout.
68-68: LGTM - Improved vertical alignment.Changing from
items-starttoitems-centerbetter 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-200ensures 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-0toshrink-0is 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-starttoitems-centercreates better visual balance between the permission description and the action button.
54-58: LGTM - Accessibility enhancement.The addition of descriptive
aria-labelattributes 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
isPendingstate (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-200improves 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
apps/desktop/src/components/main/body/sessions/outer-header/metadata/participants.tsx
Show resolved
Hide resolved
| } else if (e.key === "Backspace" && !inputValue && mappingIds.length > 0) { | ||
| const lastMappingId = mappingIds[mappingIds.length - 1]; | ||
| if (store) { | ||
| store.delRow("mapping_session_participant", lastMappingId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| {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> |
There was a problem hiding this comment.
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.
| 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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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> |
No description provided.