Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src/renderer/src/agent-chat/ref-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export function RefPicker({ query, onPick, onClose }: RefPickerProps): React.JSX
key={`${result.kind}-${result.id}`}
type="button"
role="option"
aria-selected={false}
className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm hover:bg-accent hover:text-accent-foreground"
onClick={() => onPick({ kind: result.kind, ref_id: result.id, label: result.label })}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ export const TagAutocomplete = ({
}, [])

useEffect(() => {
if (autoFocus) setTimeout(() => inputRef.current?.focus(), 100)
if (!autoFocus) return

const focusTimeout = setTimeout(() => inputRef.current?.focus(), 100)
return () => clearTimeout(focusTimeout)
}, [autoFocus])

const addTag = useCallback(
Expand Down Expand Up @@ -273,7 +276,7 @@ export const TagAutocomplete = ({
onClick={() => addTag(tag)}
onMouseEnter={() => setRequestedHighlightedIndex(idx)}
className={cn(
'flex items-center gap-2 rounded-sm py-2 px-3 mx-1 my-0.5 text-left transition-colors',
'flex items-center gap-2 rounded-sm py-2 px-3 mx-1 my-0.5 text-start transition-colors',
highlightedIndex === idx ? 'bg-[var(--tint)]/[0.03]' : 'hover:bg-foreground/[0.03]'
)}
>
Expand Down Expand Up @@ -320,7 +323,7 @@ export const TagAutocomplete = ({
onClick={() => addTag(tag.name)}
onMouseEnter={() => setRequestedHighlightedIndex(idx)}
className={cn(
'flex items-center gap-2 rounded-sm py-2 px-3 mx-1 my-0.5 text-left transition-colors',
'flex items-center gap-2 rounded-sm py-2 px-3 mx-1 my-0.5 text-start transition-colors',
highlightedIndex === idx ? 'bg-foreground/[0.03]' : 'hover:bg-foreground/[0.03]'
)}
>
Expand Down Expand Up @@ -349,7 +352,7 @@ export const TagAutocomplete = ({
onClick={() => addTag(normalized)}
onMouseEnter={() => setRequestedHighlightedIndex(idx)}
className={cn(
'flex items-center w-full py-2 px-3 gap-1.5 border-t border-border/40 text-left transition-colors',
'flex items-center w-full py-2 px-3 gap-1.5 border-t border-border/40 text-start transition-colors',
highlightedIndex === idx ? 'bg-foreground/[0.03]' : 'hover:bg-foreground/[0.03]'
)}
>
Expand Down
13 changes: 7 additions & 6 deletions apps/desktop/src/renderer/src/components/journal/note-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ export const NoteDrawer = memo(function NoteDrawer({

// Focus close button when drawer opens
useEffect(() => {
if (isOpen && closeButtonRef.current) {
// Small delay to allow animation to start
setTimeout(() => closeButtonRef.current?.focus(), 100)
}
if (!isOpen || !closeButtonRef.current) return

// Small delay to allow animation to start
const focusTimeout = setTimeout(() => closeButtonRef.current?.focus(), 100)
return () => clearTimeout(focusTimeout)
}, [isOpen])

// Handle click outside
Expand Down Expand Up @@ -106,9 +107,9 @@ export const NoteDrawer = memo(function NoteDrawer({
aria-labelledby="drawer-title"
aria-hidden={!isOpen}
className={cn(
'fixed top-0 right-0 bottom-0 z-50',
'fixed top-0 end-0 bottom-0 z-50',
'w-[40vw] min-w-[360px] max-w-[600px]',
'bg-card border-l border-border',
'bg-card border-s border-border',
'shadow-[-4px_0_20px_rgba(0,0,0,0.15)]',
'flex flex-col',
'transform transition-transform duration-250 ease-out',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ export function RevealHandler({
}
}

setTimeout(() => {
const revealTimeout = setTimeout(() => {
onReveal(pendingRevealNoteId)
}, 50)

return () => clearTimeout(revealTimeout)
}, [pendingRevealNoteId, noteMap, expandNode, onReveal, onClear])

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
SidebarMenuItem,
useSidebar
} from '@/components/ui/sidebar'
import { useDragContext } from '@/contexts/drag-context'
import { useOptionalDragContext } from '@/contexts/drag-context'
import type { Project } from '@/data/tasks-data'
import { useT } from '@memry/i18n/renderer'

Expand Down Expand Up @@ -39,14 +39,8 @@ export const SortableProjectItem = ({
const { t: tPhaseF } = useT('notes')
const { isMobile: _isMobile } = useSidebar()

// Try to get drag context - may not be available if not wrapped in DragProvider
let dragState = { isDragging: false }
try {
const context = useDragContext()
dragState = context.dragState
} catch {
// Not in DragProvider context - that's okay, just no drop zone features
}
const dragContext = useOptionalDragContext()
const dragState = dragContext?.dragState ?? { isDragging: false }

// Sortable for reordering projects
const {
Expand Down Expand Up @@ -98,7 +92,7 @@ export const SortableProjectItem = ({
>
{/* Drop indicator when hovering */}
{isOver && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-primary font-medium z-10">
<span className="absolute end-2 top-1/2 -translate-y-1/2 text-xs text-primary font-medium z-10">
{tPhaseF('phaseF.componentsSidebarSortableProjectItem.dropHere')}
</span>
)}
Expand Down
30 changes: 7 additions & 23 deletions apps/desktop/src/renderer/src/components/sync/otp-input.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
import { Loader2, Clock } from '@/lib/icons'
import { useT } from '@memry/i18n/renderer'
Expand Down Expand Up @@ -28,37 +28,21 @@ function useCountdown(
reset: () => void
} {
const [seconds, setSeconds] = useState(initialSeconds)
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

const clearTimer = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
}, [])

useEffect(() => {
if (seconds <= 0 || intervalRef.current) return clearTimer

intervalRef.current = setInterval(() => {
setSeconds((prev) => {
if (prev <= 1) {
clearTimer()
return 0
}
if (seconds <= 0) return

return prev - 1
})
const countdownTimeout = setTimeout(() => {
setSeconds((prev) => Math.max(prev - 1, 0))
}, 1000)

return clearTimer
}, [seconds, clearTimer])
return () => clearTimeout(countdownTimeout)
}, [seconds])

const reset = useCallback(() => {
onResend()
clearTimer()
setSeconds(60)
}, [onResend, clearTimer])
}, [onResend])

return { seconds, canResend: seconds === 0, reset }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo, useEffect, useCallback, useRef } from 'react'
import { useState, useMemo, useEffect, useCallback, useRef, useId } from 'react'
import { Calendar as CalendarIcon, Star, X, Clock, Plus, Sun } from '@/lib/icons'

import { Button } from '@/components/ui/button'
Expand Down Expand Up @@ -176,6 +176,7 @@ export const DueDatePicker = ({
const [inputValue, setInputValue] = useState('') // Track input value for conditional shortcuts
const triggerRef = useRef<HTMLButtonElement>(null)
const naturalDateInputRef = useRef<NaturalDateInputRef>(null)
const pickerContentId = useId()

const quickOptions = useMemo(() => getQuickDateOptions(), [])

Expand Down Expand Up @@ -323,22 +324,23 @@ export const DueDatePicker = ({
ref={triggerRef}
variant="outline"
role="combobox"
aria-controls={pickerContentId}
aria-expanded={isOpen}
aria-label={tPhaseF('phaseF.componentsTasksDueDatePicker.selectDueDate')}
className={cn(
'w-full justify-start text-left font-normal',
'w-full justify-start text-start font-normal',
!date && 'text-muted-foreground',
dateDisplay && statusColors[dateDisplay.status],
className
)}
>
<CalendarIcon className="mr-2 size-4 shrink-0" />
<CalendarIcon className="me-2 size-4 shrink-0" />
{date ? (
<span className="truncate">
{dateDisplay?.text}
{time && <span className="ml-1 text-muted-foreground">· {formatTime(time)}</span>}
{time && <span className="ms-1 text-muted-foreground">· {formatTime(time)}</span>}
{dateDisplay?.status === 'overdue' && (
<span className="ml-1 text-xs opacity-80">
<span className="ms-1 text-xs opacity-80">
· {tPhaseF('phaseF.componentsTasksDueDatePicker.overdue')}
</span>
)}
Expand All @@ -349,7 +351,7 @@ export const DueDatePicker = ({
</Button>
</PopoverTrigger>

<PopoverContent className="w-80 p-0" align="start">
<PopoverContent id={pickerContentId} className="w-80 p-0" align="start">
{!showCalendar ? (
<div className="flex flex-col">
{/* Natural Language Input */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo, useCallback } from 'react'
import { useState, useMemo, useCallback, useId } from 'react'
import { RefreshCw, ChevronDown, Check } from '@/lib/icons'

import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
Expand Down Expand Up @@ -37,6 +37,7 @@ export const RepeatPicker = ({
const { t: tPhaseF } = useT('tasks')
const { t } = useT('common')
const [isOpen, setIsOpen] = useState(false)
const optionsId = useId()

// Generate presets based on due date
const presets = useMemo(() => getRepeatPresets(dueDate), [dueDate])
Expand Down Expand Up @@ -80,6 +81,7 @@ export const RepeatPicker = ({
<Button
variant="outline"
role="combobox"
aria-controls={optionsId}
aria-expanded={isOpen}
aria-label={tPhaseF('phaseF.componentsTasksRepeatPicker.selectRepeatFrequency')}
disabled={disabled}
Expand All @@ -103,7 +105,7 @@ export const RepeatPicker = ({
</Button>
</PopoverTrigger>

<PopoverContent className="w-[280px] p-1" align="start">
<PopoverContent id={optionsId} className="w-[280px] p-1" align="start">
{/* Does not repeat option */}
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react'
import { PopoverContent } from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { usePickerContext } from './types'

export interface PickerContentProps extends React.ComponentPropsWithoutRef<typeof PopoverContent> {
width?: 'auto' | 'trigger' | number
Expand All @@ -10,6 +11,7 @@ export const PickerContent = React.forwardRef<
React.ComponentRef<typeof PopoverContent>,
PickerContentProps
>(({ width, className, children, ...props }, ref) => {
const { contentId } = usePickerContext()
const widthClass =
width === 'auto'
? 'w-auto'
Expand All @@ -22,6 +24,7 @@ export const PickerContent = React.forwardRef<
return (
<PopoverContent
ref={ref}
id={props.id ?? contentId}
data-slot="picker-content"
className={cn(
'p-0 rounded-md overflow-clip shadow-[var(--shadow-card-hover)]',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo, useCallback } from 'react'
import { useState, useMemo, useCallback, useId } from 'react'
import { Popover } from '@/components/ui/popover'
import { PickerContext, type PickerMode } from './types'

Expand Down Expand Up @@ -26,6 +26,7 @@ export function PickerRoot({
const [internalOpen, setInternalOpen] = useState(defaultOpen)
const [searchQuery, setSearchQuery] = useState('')
const [activePanel, setActivePanel] = useState<string | null>(null)
const contentId = useId()

const isControlled = controlledOpen !== undefined
const open = isControlled ? controlledOpen : internalOpen
Expand Down Expand Up @@ -55,6 +56,7 @@ export function PickerRoot({
const ctx = useMemo(
() => ({
open,
contentId,
onOpenChange: handleOpenChange,
mode,
value,
Expand All @@ -64,7 +66,7 @@ export function PickerRoot({
activePanel,
onPanelChange: setActivePanel
}),
[open, handleOpenChange, mode, value, handleValueChange, searchQuery, activePanel]
[open, contentId, handleOpenChange, mode, value, handleValueChange, searchQuery, activePanel]
)

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface PickerTriggerProps

export const PickerTrigger = React.forwardRef<HTMLButtonElement, PickerTriggerProps>(
({ variant, chevron = false, asChild = false, className, children, onClick, ...props }, ref) => {
const { open } = usePickerContext()
const { open, contentId } = usePickerContext()

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
Expand All @@ -51,6 +51,7 @@ export const PickerTrigger = React.forwardRef<HTMLButtonElement, PickerTriggerPr
ref={ref}
type="button"
role="combobox"
aria-controls={contentId}
aria-expanded={open}
data-slot="picker-trigger"
className={cn(pickerTriggerVariants({ variant }), className)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type PickerMode = 'single' | 'multi'

export interface PickerContextValue {
open: boolean
contentId: string
onOpenChange: (open: boolean) => void
mode: PickerMode
value: string | string[] | null
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/renderer/src/contexts/drag-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ export const useDragContext = (): DragContextValue => {
return context
}

export const useOptionalDragContext = (): DragContextValue | null => useContext(DragContext)

// ============================================================================
// PROVIDER
// ============================================================================
Expand Down
11 changes: 9 additions & 2 deletions apps/desktop/src/renderer/src/pages/settings/ai-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ export function AISettings() {
}, [])

useEffect(() => {
let completeTimeout: ReturnType<typeof setTimeout> | null = null

const unsubscribe = window.api.onEmbeddingProgress((event) => {
if (event.phase === 'downloading' || event.phase === 'loading') {
setIsLoadingModel(true)
Expand All @@ -124,15 +126,20 @@ export function AISettings() {
} else {
setReindexProgress(event)
if (event.phase === 'complete') {
setTimeout(() => {
if (completeTimeout) clearTimeout(completeTimeout)
completeTimeout = setTimeout(() => {
setIsReindexing(false)
setReindexProgress(null)
void window.api.settings.getAIModelStatus().then(setModelStatus)
}, 1000)
}
}
})
return unsubscribe

return () => {
if (completeTimeout) clearTimeout(completeTimeout)
unsubscribe()
}
}, [t])

useEffect(() => {
Expand Down
11 changes: 11 additions & 0 deletions apps/landing/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ h6 {
opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}

.text-balance {
text-wrap: balance;
}
Expand Down
Loading
Loading