feat(file-explorer): add parallel thumbnail loading and file preview

- Implement parallel thumbnail queue (2 concurrent) for gallery view
- Add BottomSheetModal file preview with 80% snap point
- Add pull-to-refresh for directory listings
- Fix image loading race conditions with Set-based in-flight tracking

Also includes:
- message-input: add Cmd+Shift+D dictation toggle shortcut
- message: update user message bubble to darker zinc color
- Add PRODUCTION.md deployment guide

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-01-05 08:41:43 +07:00
co-authored by Claude Opus 4.5
parent edb3258ae9
commit 9d7e44851c
4 changed files with 749 additions and 158 deletions
+28 -5
View File
@@ -189,22 +189,30 @@ export function MessageInput({
enableDuration: true,
});
// Cmd+D to toggle dictation on web
// Cmd+D to start/submit dictation, Escape to cancel
useEffect(() => {
if (!IS_WEB) return;
function handleKeyDown(event: KeyboardEvent) {
// Cmd+D: start dictation or submit if already dictating
if ((event.metaKey || event.ctrlKey) && event.key === "d") {
event.preventDefault();
if (isDictating) {
cancelDictation();
sendAfterTranscriptRef.current = true;
confirmDictation();
} else {
startDictation();
}
return;
}
// Escape: cancel dictation
if (event.key === "Escape" && isDictating) {
event.preventDefault();
cancelDictation();
}
}
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [isDictating, cancelDictation, startDictation]);
}, [isDictating, cancelDictation, confirmDictation, startDictation]);
// Animate overlay
useEffect(() => {
@@ -355,17 +363,25 @@ export function MessageInput({
if (!shouldHandleDesktopSubmit) return;
const { shiftKey, metaKey, ctrlKey } = event.nativeEvent;
// Cmd+D or Ctrl+D: toggle dictation
// Cmd+D or Ctrl+D: start dictation or submit if already dictating
if ((metaKey || ctrlKey) && event.nativeEvent.key === "d") {
event.preventDefault();
if (isDictating) {
cancelDictation();
sendAfterTranscriptRef.current = true;
confirmDictation();
} else {
startDictation();
}
return;
}
// Escape: cancel dictation
if (event.nativeEvent.key === "Escape" && isDictating) {
event.preventDefault();
cancelDictation();
return;
}
if (event.nativeEvent.key !== "Enter") return;
// Shift+Enter: add newline (default behavior, don't intercept)
@@ -561,6 +577,13 @@ const styles = StyleSheet.create(((theme: any) => ({
xs: theme.spacing[3],
md: theme.spacing[4],
},
...(IS_WEB
? {
transitionProperty: "border-color",
transitionDuration: "200ms",
transitionTimingFunction: "ease-in-out",
}
: {}),
},
imagePreviewContainer: {
flexDirection: "row",
+2 -2
View File
@@ -45,7 +45,7 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({
paddingHorizontal: theme.spacing[4],
},
bubble: {
backgroundColor: theme.colors.primary,
backgroundColor: theme.colors.muted,
borderRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius.sm,
paddingHorizontal: theme.spacing[4],
@@ -53,7 +53,7 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({
maxWidth: "80%",
},
text: {
color: theme.colors.primaryForeground,
color: theme.colors.foreground,
fontSize: theme.fontSize.lg,
lineHeight: 24,
},