diff --git a/.Jules/palette.md b/.Jules/palette.md new file mode 100644 index 00000000..b174b782 --- /dev/null +++ b/.Jules/palette.md @@ -0,0 +1,3 @@ +## 2025-05-14 - [Icon-only Button Accessibility] +**Learning:** Many icon-only buttons in the application were missing ARIA labels and titles, making them inaccessible to screen readers and providing no tooltips for sighted users. The logo functioning as a history toggle was particularly non-obvious. +**Action:** Always provide both `aria-label` and `title` for icon-only buttons. Ensure that branding elements used as interactive controls have clear descriptive labels. diff --git a/components/chat-panel.tsx b/components/chat-panel.tsx index ca2fbc6f..b999803b 100644 --- a/components/chat-panel.tsx +++ b/components/chat-panel.tsx @@ -7,6 +7,7 @@ import { cn } from '@/lib/utils' import { UserMessage } from './user-message' import { Button } from './ui/button' import { ArrowRight, Plus, Paperclip, X, Sprout } from 'lucide-react' +import { Spinner } from './ui/spinner' import Textarea from 'react-textarea-autosize' import { nanoid } from '@/lib/utils' import { useSettingsStore } from '@/lib/store/settings' @@ -20,6 +21,7 @@ interface ChatPanelProps { input: string setInput: (value: string) => void onSuggestionsChange?: (suggestions: PartialRelated | null) => void + onSubmitting?: (isSubmitting: boolean) => void } export interface ChatPanelRef { @@ -27,12 +29,13 @@ export interface ChatPanelRef { submitForm: () => void } -export const ChatPanel = forwardRef(({ messages, input, setInput, onSuggestionsChange }, ref) => { +export const ChatPanel = forwardRef(({ messages, input, setInput, onSuggestionsChange, onSubmitting }, ref) => { const [, setMessages] = useUIState() const { submit, clearChat } = useActions() const { mapProvider } = useSettingsStore() const [isMobile, setIsMobile] = useState(false) const [selectedFile, setSelectedFile] = useState(null) + const [isSubmitting, setIsSubmitting] = useState(false) const [suggestions, setSuggestionsState] = useState(null) const setSuggestions = useCallback((s: PartialRelated | null) => { setSuggestionsState(s) @@ -121,8 +124,15 @@ export const ChatPanel = forwardRef(({ messages, i setInput('') clearAttachment() - const responseMessage = await submit(formData) - setMessages(currentMessages => [...currentMessages, responseMessage as any]) + setIsSubmitting(true) + onSubmitting?.(true) + try { + const responseMessage = await submit(formData) + setMessages(currentMessages => [...currentMessages, responseMessage as any]) + } finally { + setIsSubmitting(false) + onSubmitting?.(false) + } } const handleClear = async () => { @@ -177,6 +187,7 @@ export const ChatPanel = forwardRef(({ messages, i onClick={() => handleClear()} data-testid="new-chat-button" title="New Chat" + aria-label="New Chat" > @@ -225,6 +236,8 @@ export const ChatPanel = forwardRef(({ messages, i )} onClick={handleAttachmentClick} data-testid="desktop-attachment-button" + aria-label="Attach File" + title="Attach File" > @@ -281,11 +294,12 @@ export const ChatPanel = forwardRef(({ messages, i 'absolute top-1/2 transform -translate-y-1/2', isMobile ? 'right-1' : 'right-2' )} - disabled={input.length === 0 && !selectedFile} + disabled={(input.length === 0 && !selectedFile) || isSubmitting} aria-label="Send message" + title="Send Message" data-testid="chat-submit" > - + {isSubmitting ? : } @@ -295,7 +309,14 @@ export const ChatPanel = forwardRef(({ messages, i {selectedFile.name} - diff --git a/components/chat.tsx b/components/chat.tsx index e675f124..1c617891 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -38,6 +38,7 @@ export function Chat({ id }: ChatProps) { const [input, setInput] = useState('') const [showEmptyScreen, setShowEmptyScreen] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) + const [isSubmittingAction, setIsSubmittingAction] = useState(false) const [suggestions, setSuggestions] = useState(null) const chatPanelRef = useRef(null); @@ -132,7 +133,11 @@ export function Chat({ id }: ChatProps) { {activeView ? : isUsageOpen ? : }
- +
@@ -185,6 +191,7 @@ export function Chat({ id }: ChatProps) { input={input} setInput={setInput} onSuggestionsChange={setSuggestions} + onSubmitting={setIsSubmittingAction} />
diff --git a/components/header-search-button.tsx b/components/header-search-button.tsx index 04896457..e65e86f3 100644 --- a/components/header-search-button.tsx +++ b/components/header-search-button.tsx @@ -154,6 +154,7 @@ export function HeaderSearchButton() { onClick={handleResolutionSearch} disabled={isAnalyzing || !map || !actions} title="Analyze current map view" + aria-label="Analyze current map view" > {isAnalyzing ? (
diff --git a/components/header.tsx b/components/header.tsx index fd80bc44..97f92471 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -52,7 +52,14 @@ export const Header = () => {
-
- @@ -89,7 +109,13 @@ export const Header = () => { {/* Mobile menu buttons */}
- diff --git a/components/history.tsx b/components/history.tsx index 5bae1a39..b81d5f05 100644 --- a/components/history.tsx +++ b/components/history.tsx @@ -19,6 +19,8 @@ export function History({ location }: HistoryProps) { })} data-testid="history-button" onClick={toggleHistory} + aria-label="Toggle History" + title="Toggle History" > {location === 'header' ? : } diff --git a/components/mobile-icons-bar.tsx b/components/mobile-icons-bar.tsx index d0db2cfa..8fe259ff 100644 --- a/components/mobile-icons-bar.tsx +++ b/components/mobile-icons-bar.tsx @@ -19,13 +19,15 @@ import { MapToggle } from './map-toggle' import { ModeToggle } from './mode-toggle' import { ProfileToggle } from './profile-toggle' import { useCalendarToggle } from './calendar-toggle-context' +import { Spinner } from './ui/spinner' interface MobileIconsBarProps { onAttachmentClick: () => void; onSubmitClick: () => void; + isSubmitting?: boolean; } -export const MobileIconsBar: React.FC = ({ onAttachmentClick, onSubmitClick }) => { +export const MobileIconsBar: React.FC = ({ onAttachmentClick, onSubmitClick, isSubmitting }) => { const [, setMessages] = useUIState() const { clearChat } = useActions() const { toggleCalendar } = useCalendarToggle() @@ -37,27 +39,71 @@ export const MobileIconsBar: React.FC = ({ onAttachmentClic return (
- - - - - -