Skip to content

Conversation

@Kitenite
Copy link
Collaborator

@Kitenite Kitenite commented Jan 9, 2026

Summary

  • Refactor store.ts (924 → 675 lines, -27%) by extracting file viewer and split actions
  • Refactor prompt-input.tsx (1413 → 1272 lines, -10%) by extracting speech recognition hook and utilities
  • Create reusable useAttachments hook for attachment management

Changes

Desktop - Tabs Store

File Change
store.ts -249 lines (use extracted actions)
utils.ts +53 lines (add findNextTab)
actions/file-viewer-actions.ts New (174 lines)
actions/split-actions.ts New (95 lines)

UI - Prompt Input

File Change
prompt-input.tsx -141 lines (use extracted hook)
hooks/use-speech-recognition.ts New (174 lines)
hooks/use-attachments.ts New (183 lines)
utils/blob-to-data-url.ts New (21 lines)

Test plan

  • bun run typecheck passes
  • bun run lint:fix passes
  • Test desktop app tab/pane operations
  • Test speech recognition in prompt input
  • Test file attachments in prompt input

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • File viewer pane management: open, focus, and pin files within tabs.
    • Terminal pane splitting: divide panes horizontally or vertically.
    • File attachment support with validation (file count, size, and type limits).
    • Speech recognition integration for text input in prompt fields.
  • Improvements

    • Enhanced tab switching behavior when closing tabs.

✏️ Tip: You can customize this high-level summary in your review settings.

Refactor two high-priority large files to improve maintainability:

**store.ts (924 → 675 lines, -27%)**
- Extract `addFileViewerPane` logic to `actions/file-viewer-actions.ts`
- Extract split operations to `actions/split-actions.ts`
- Move `findNextTab` helper to `utils.ts`

**prompt-input.tsx (1413 → 1272 lines, -10%)**
- Extract speech recognition to `hooks/use-speech-recognition.ts`
- Create `hooks/use-attachments.ts` for reusable attachment management
- Extract blob URL conversion to `utils/blob-to-data-url.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

This PR adds new file viewer pane management and pane splitting actions while refactoring the tabs store to delegate logic to dedicated action modules. It also introduces attachment and speech recognition hooks with supporting utilities for the AI elements UI components.

Changes

Cohort / File(s) Summary
Tabs Store - File Viewer & Pane Management
apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
New action addFileViewerPaneAction manages opening/focusing file viewer panes with logic to reuse pinned panes, repurpose preview panes, or create new ones. Handles pin state transitions and auto-computed viewMode (diff, rendered markdown, or raw).
Tabs Store - Pane Splitting
apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
New actions splitPane, splitPaneVertical, and splitPaneHorizontal enable pane splitting with terminal pane creation and 50% split defaults. Validates tab/pane existence and updates mosaic layout.
Tabs Store - Core Refactoring
apps/desktop/src/renderer/stores/tabs/store.ts
Refactors inline pane/layout logic to delegated action calls; reduces ~250 lines by extracting file-viewer and split operations. Updates removeTab to use new findNextTab utility for tab activation on close.
Tabs Store - Utilities
apps/desktop/src/renderer/stores/tabs/utils.ts
New findNextTab function determines next active tab when closing, applying three-tier priority: most-recent workspace tab, adjacent tab by position, or first workspace tab.
AI Elements - Attachment Hook
packages/ui/src/components/ai-elements/hooks/use-attachments.ts
New hook manages file attachments with validation (MIME type, size, count), blob URL lifecycle, and error signaling. Provides add, remove, clear, and openFileDialog operations with cleanup on unmount.
AI Elements - Speech Recognition Hook
packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts
New hook wraps browser SpeechRecognition API with transcript accumulation, textarea integration, and toggleListening control. Reports isSupported and isListening state.
AI Elements - Hooks Exports
packages/ui/src/components/ai-elements/hooks/index.ts
Centralizes public API by re-exporting attachment and speech recognition hooks with their types.
AI Elements - Utilities
packages/ui/src/components/ai-elements/utils/blob-to-data-url.ts, packages/ui/src/components/ai-elements/utils/index.ts
New utility convertBlobUrlToDataUrl converts blob URLs to base64 data URLs asynchronously; exported via utils index.
AI Elements - Prompt Input Refactoring
packages/ui/src/components/ai-elements/prompt-input.tsx
Simplifies by replacing inline SpeechRecognition state with useSpeechRecognition hook and delegating blob URL conversion to utility; removes ~140 lines of manual lifecycle code.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

Poem

🐰 Panes split clean, attachments held tight,
Speech whispers truth through the data's light,
Tabs find their way with history in hand,
Actions modular, a well-architected land! 🎯✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main refactoring objective: extracting actions and hooks from large files to improve code organization and maintainability.
Description check ✅ Passed The description is comprehensive and well-structured, covering summary, detailed file changes with metrics, and a test plan. It follows the template with Type of Change checked and provides clear context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Caution

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

⚠️ Outside diff range comments (1)
packages/ui/src/components/ai-elements/prompt-input.tsx (1)

713-755: Silent error handling in Promise chain.

The .catch blocks at lines 739-741 and 753-755 silently swallow errors without any logging. Per coding guidelines, errors should at minimum be logged with context.

Suggested fix
 					if (result instanceof Promise) {
 						result
 							.then(() => {
 								clear();
 								if (usingProvider) {
 									controller.textInput.clear();
 								}
 							})
-							.catch(() => {
+							.catch((error) => {
+								console.error("[prompt-input] Submit handler error", error);
 								// Don't clear on error - user may want to retry
 							});
 					} else {
 						// ...
 					}
-				} catch {
+				} catch (error) {
+					console.error("[prompt-input] Submit handler error", error);
 					// Don't clear on error - user may want to retry
 				}
 			})
-			.catch(() => {
+			.catch((error) => {
+				console.error("[prompt-input] Blob URL conversion error", error);
 				// Don't clear on error - user may want to retry
 			});
🧹 Nitpick comments (6)
apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts (1)

100-109: Duplicated markdown extension check.

This logic duplicates the MARKDOWN_EXTENSIONS constant and check from utils.ts (lines 10, 130-134 in createFileViewerPane). Consider extracting a shared helper like isMarkdownFile(filePath) to keep the logic DRY.

♻️ Suggested refactor

In utils.ts, add:

export const isMarkdownFile = (filePath: string): boolean =>
  MARKDOWN_EXTENSIONS.some((ext) => filePath.endsWith(ext));

Then in file-viewer-actions.ts:

+import { isMarkdownFile } from "../utils";

-		} else if (
-			options.filePath.endsWith(".md") ||
-			options.filePath.endsWith(".markdown") ||
-			options.filePath.endsWith(".mdx")
-		) {
+		} else if (isMarkdownFile(options.filePath)) {
apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts (1)

16-23: Consider using object parameters per coding guidelines.

The function has 6 parameters. Per the coding guidelines for **/*.{ts,tsx}: "Use object parameters for functions with 2+ parameters instead of positional arguments."

♻️ Suggested refactor
+interface SplitPaneParams {
+	state: TabsState;
+	tabId: string;
+	sourcePaneId: string;
+	direction: "row" | "column";
+	path?: MosaicBranch[];
+	options?: CreatePaneOptions;
+}

-export function splitPane(
-	state: TabsState,
-	tabId: string,
-	sourcePaneId: string,
-	direction: "row" | "column",
-	path?: MosaicBranch[],
-	options?: CreatePaneOptions,
-): SplitResult | null {
+export function splitPane({
+	state,
+	tabId,
+	sourcePaneId,
+	direction,
+	path,
+	options,
+}: SplitPaneParams): SplitResult | null {

Apply similar changes to splitPaneVertical and splitPaneHorizontal.

packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts (2)

107-109: Extract hardcoded language to a named constant or make it configurable.

The language "en-US" is hardcoded. Consider extracting it as a module-level constant or adding it as an option to UseSpeechRecognitionOptions for flexibility.

Suggested improvement
+const DEFAULT_LANGUAGE = "en-US";
+
 export interface UseSpeechRecognitionOptions {
 	textareaRef?: RefObject<HTMLTextAreaElement | null>;
 	onTranscriptionChange?: (text: string) => void;
+	language?: string;
 }

Then use it:

-			speechRecognition.lang = "en-US";
+			speechRecognition.lang = language ?? DEFAULT_LANGUAGE;

98-155: Potential recognition restart if onTranscriptionChange is not memoized.

The useEffect recreates the SpeechRecognition instance whenever textareaRef or onTranscriptionChange changes. If the consumer passes an inline callback without useCallback, this will create a new instance on every render, potentially interrupting active speech recognition.

Consider using a ref for the callback to avoid this issue, or document that onTranscriptionChange should be memoized.

Suggested fix using a ref for the callback
 export function useSpeechRecognition({
 	textareaRef,
 	onTranscriptionChange,
 }: UseSpeechRecognitionOptions): UseSpeechRecognitionResult {
 	const [isListening, setIsListening] = useState(false);
 	const [recognition, setRecognition] = useState<SpeechRecognition | null>(
 		null,
 	);
 	const recognitionRef = useRef<SpeechRecognition | null>(null);
+	const onTranscriptionChangeRef = useRef(onTranscriptionChange);
+	onTranscriptionChangeRef.current = onTranscriptionChange;

 	useEffect(() => {
 		// ... setup code ...

 				if (finalTranscript && textareaRef?.current) {
 					// ...
-					onTranscriptionChange?.(newValue);
+					onTranscriptionChangeRef.current?.(newValue);
 				}
 			};

 		// ...
-	}, [textareaRef, onTranscriptionChange]);
+	}, [textareaRef]);
packages/ui/src/components/ai-elements/hooks/use-attachments.ts (1)

83-132: Consider logging rejected files for debugging purposes.

The add function correctly reports errors via onError, but when some files are rejected (e.g., wrong type or size) while others are accepted, there's no visibility into which specific files failed. Adding debug logging would help troubleshoot issues.

Optional: Add debug logging
 const add = useCallback(
 	(fileList: File[] | FileList) => {
 		const incoming = Array.from(fileList);
 		const accepted = incoming.filter((f) => matchesAccept(f));
 		if (incoming.length && accepted.length === 0) {
+			console.debug("[attachments] No files matched accept pattern", { accept, files: incoming.map(f => f.name) });
 			onError?.({
 				code: "accept",
 				message: "No files match the accepted types.",
 			});
 			return;
 		}
packages/ui/src/components/ai-elements/prompt-input.tsx (1)

493-589: Consider using useAttachments hook to reduce code duplication.

The matchesAccept, addLocal, removeLocal, and clearLocal functions duplicate the logic in the newly created useAttachments hook. While the dual-mode architecture (provider vs local) may warrant keeping local state management in the component, the validation and blob URL lifecycle logic could be unified.

If integration is not pursued, consider documenting why the duplication is intentional.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e43658 and 4c810f4.

📒 Files selected for processing (10)
  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • packages/ui/src/components/ai-elements/hooks/index.ts
  • packages/ui/src/components/ai-elements/hooks/use-attachments.ts
  • packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts
  • packages/ui/src/components/ai-elements/prompt-input.tsx
  • packages/ui/src/components/ai-elements/utils/blob-to-data-url.ts
  • packages/ui/src/components/ai-elements/utils/index.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use object parameters for functions with 2+ parameters instead of positional arguments
Functions with 2+ parameters should accept a single params object with named properties for self-documentation and extensibility
Use prefixed console logging with context pattern: [domain/operation] message
Extract magic numbers and hardcoded values to named constants at module top
Use lookup objects/maps instead of repeated if (type === ...) conditionals
Avoid using any type - maintain type safety in TypeScript code
Never swallow errors silently - at minimum log them with context
Import from concrete files directly when possible - avoid barrel file abuse that creates circular dependencies
Avoid deep nesting (4+ levels) - use early returns, extract functions, and invert conditions
Use named properties in options objects instead of boolean parameters to avoid boolean blindness

Files:

  • packages/ui/src/components/ai-elements/hooks/index.ts
  • packages/ui/src/components/ai-elements/utils/index.ts
  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • packages/ui/src/components/ai-elements/prompt-input.tsx
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts
  • packages/ui/src/components/ai-elements/hooks/use-attachments.ts
  • packages/ui/src/components/ai-elements/utils/blob-to-data-url.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Biome for formatting and linting - run at root level with bun run lint:fix or biome check --write

Files:

  • packages/ui/src/components/ai-elements/hooks/index.ts
  • packages/ui/src/components/ai-elements/utils/index.ts
  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • packages/ui/src/components/ai-elements/prompt-input.tsx
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts
  • packages/ui/src/components/ai-elements/hooks/use-attachments.ts
  • packages/ui/src/components/ai-elements/utils/blob-to-data-url.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net) in renderer process or shared code - they are externalized for browser compatibility

Files:

  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Drizzle ORM for all database operations - never use raw SQL

Files:

  • apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts
  • apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

One component per file - do not create multi-component files

Files:

  • packages/ui/src/components/ai-elements/prompt-input.tsx
🧠 Learnings (1)
📚 Learning: 2026-01-02T06:50:28.671Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-02T06:50:28.671Z
Learning: Applies to apps/*/src/components/{ui,ai-elements,react-flow}/*.tsx : Use kebab-case single files for shadcn/ui components (e.g., button.tsx, base-node.tsx) in src/components/ui/, src/components/ai-elements, and src/components/react-flow/

Applied to files:

  • packages/ui/src/components/ai-elements/hooks/index.ts
  • packages/ui/src/components/ai-elements/prompt-input.tsx
🧬 Code graph analysis (7)
apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (2)
  • TabsState (26-28)
  • AddFileViewerPaneOptions (41-52)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
  • extractPaneIdsFromLayout (54-65)
  • createFileViewerPane (120-158)
apps/desktop/src/shared/types/mosaic.ts (1)
  • MosaicNode (1-8)
packages/ui/src/components/ai-elements/prompt-input.tsx (1)
packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts (1)
  • useSpeechRecognition (88-174)
apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts (3)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • TabsState (26-28)
apps/desktop/src/renderer/stores/tabs/utils.ts (2)
  • CreatePaneOptions (73-76)
  • createPane (81-97)
apps/desktop/src/shared/types/mosaic.ts (1)
  • MosaicNode (1-8)
packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts (1)
packages/ui/src/components/ai-elements/hooks/index.ts (3)
  • UseSpeechRecognitionOptions (9-9)
  • UseSpeechRecognitionResult (10-10)
  • useSpeechRecognition (11-11)
packages/ui/src/components/ai-elements/utils/blob-to-data-url.ts (2)
packages/ui/src/components/ai-elements/utils/index.ts (1)
  • convertBlobUrlToDataUrl (1-1)
apps/desktop/src/main/lib/api-client.ts (1)
  • fetch (25-39)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (18-20)
apps/desktop/src/renderer/stores/tabs/store.ts (2)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
  • findNextTab (432-477)
apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts (1)
  • addFileViewerPaneAction (17-142)
⏰ 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). (6)
  • GitHub Check: Deploy API
  • GitHub Check: Deploy Marketing
  • GitHub Check: Deploy Admin
  • GitHub Check: Deploy Docs
  • GitHub Check: Deploy Web
  • GitHub Check: Build
🔇 Additional comments (15)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)

424-477: Well-structured tab selection logic.

The three-tier priority (history → position → fallback) is a good UX pattern. The implementation correctly filters by workspace and handles edge cases.

Minor observation: The checks allWorkspaceTabs[nextIndex].id !== tabIdToClose (line 466) and allWorkspaceTabs[prevIndex].id !== tabIdToClose (line 470) are redundant since nextIndex and prevIndex are computed from currentIndex, and the tab at currentIndex is the one being closed—not the adjacent ones. This is harmless defensive code but could be simplified.

apps/desktop/src/renderer/stores/tabs/actions/file-viewer-actions.ts (2)

17-48: Clean action pattern with proper state derivation.

The pinned pane lookup correctly matches on filePath, diffCategory, and commitHash to identify exact duplicates. Returning both result and paneId is a good pattern that separates state changes from the returned identifier.


144-174: Well-encapsulated helper function.

createNewFileViewerPane properly delegates to createFileViewerPane from utils and constructs the mosaic layout. The immutable state update pattern is correctly applied.

apps/desktop/src/renderer/stores/tabs/store.ts (4)

5-10: Clean import organization for action modules.

The imports are well-organized, separating action modules from utilities. The aliasing of splitPaneVertical as splitPaneVerticalAction avoids naming conflicts with the store methods.

Also applies to: 18-18


347-353: Clean delegation to addFileViewerPaneAction.

The store now correctly delegates to the extracted action and applies the result via set(result). This maintains the same public API while improving maintainability.


548-568: Consistent pattern for split actions.

Both splitPaneVertical and splitPaneHorizontal follow the same delegation pattern, correctly checking for null before applying state updates. This aligns with the action module's contract of returning null when validation fails.


104-108: Delegation to findNextTab simplifies removeTab.

The refactored code correctly delegates tab selection to findNextTab. The type system properly supports this pattern: findNextTab returns string | null (which can be null when no tabs remain in the workspace), and activeTabIds is typed as Record<string, string | null>, making the assignment type-safe.

apps/desktop/src/renderer/stores/tabs/actions/split-actions.ts (2)

24-69: Solid split implementation with proper validation.

The function correctly:

  • Validates both tab existence and pane ownership (line 28)
  • Uses updateTree for path-based splits, preserving existing layout structure
  • Falls back to wrapping the entire layout when no path is provided
  • Returns immutable state updates

71-95: Clean convenience wrappers.

splitPaneVertical and splitPaneHorizontal are thin wrappers that map to the appropriate direction. The JSDoc comments correctly describe the visual result ("side by side" vs "stacked").

packages/ui/src/components/ai-elements/hooks/use-speech-recognition.ts (1)

88-173: LGTM - Well-structured hook with proper cleanup.

The hook correctly:

  • Uses object parameters per coding guidelines
  • Uses prefixed console logging [speech-recognition]
  • Handles browser API availability gracefully
  • Cleans up the recognition instance on unmount
packages/ui/src/components/ai-elements/hooks/use-attachments.ts (1)

44-183: LGTM - Solid attachment management hook.

The hook properly:

  • Uses object parameters with sensible defaults
  • Validates file types (including wildcard patterns), sizes, and counts
  • Manages blob URL lifecycle with proper cleanup on removal and unmount
  • Uses the ref pattern to avoid stale closures in cleanup
packages/ui/src/components/ai-elements/utils/index.ts (1)

1-1: LGTM - Clean re-export.

Simple barrel file with a single re-export. No issues.

packages/ui/src/components/ai-elements/hooks/index.ts (1)

1-12: LGTM - Clean public API surface.

The barrel file properly consolidates types and functions from both hooks, providing a clean public API for consumers.

packages/ui/src/components/ai-elements/prompt-input.tsx (2)

72-73: LGTM - Clean integration of extracted hook and utility.

The imports correctly reference the new extracted modules, enabling the speech recognition hook and blob URL utility to be used in the component.


1055-1068: LGTM - Speech recognition hook integration.

The PromptInputSpeechButton now cleanly uses the extracted useSpeechRecognition hook, replacing previous inline logic. The isSupported check properly disables the button when the browser doesn't support speech recognition.

Comment on lines +14 to +20
reader.onloadend = () => resolve(reader.result as string);
reader.onerror = () => resolve(null);
reader.readAsDataURL(blob);
});
} catch {
return null;
}
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

Errors are swallowed silently - add logging with context.

Per coding guidelines, errors should not be swallowed silently. The catch block and onerror handler both return null without any logging, making it difficult to debug conversion failures.

Suggested fix
 export async function convertBlobUrlToDataUrl(
 	url: string,
 ): Promise<string | null> {
 	try {
 		const response = await fetch(url);
 		const blob = await response.blob();
 		return new Promise((resolve) => {
 			const reader = new FileReader();
 			reader.onloadend = () => resolve(reader.result as string);
-			reader.onerror = () => resolve(null);
+			reader.onerror = () => {
+				console.error("[blob-to-data-url] FileReader error", { url });
+				resolve(null);
+			};
 			reader.readAsDataURL(blob);
 		});
-	} catch {
+	} catch (error) {
+		console.error("[blob-to-data-url] Fetch error", { url, error });
 		return null;
 	}
 }

@github-actions
Copy link

github-actions bot commented Jan 9, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

@Kitenite Kitenite closed this Jan 10, 2026
@Kitenite Kitenite deleted the all-the-tests branch January 12, 2026 18:32
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.

2 participants