## CHANGES

- Added Windows-path truncation coverage so session lists display correctly everywhere 🪟
- Improved code-fence language parsing to support C++ mapping to cpp 🧠
- Refined About modal stats copy with clearer “Hands-on Time” labeling ⏱️
- Simplified mobile connection timeout typing for safer browser compatibility 🔧
- Centralized haptic triggering into shared constants for cleaner reuse 📦
- Upgraded haptics detection to verify vibrate is an actual function 📳
- Tuned input-mode toggle haptics to a crisp light vibration duration 🎚️
- Strengthened interrupt button feedback with consistent strong haptic pulses 🛑
- Removed redundant default export bundle from CommandInputButtons module 🧹
- Expanded test suite to catch edge cases across platforms and languages 
This commit is contained in:
Pedram Amini
2025-12-21 16:09:13 -06:00
parent c7c1c8f4d6
commit 5df0edbc8a
6 changed files with 29 additions and 32 deletions

View File

@@ -239,6 +239,14 @@ describe('AllSessionsView', () => {
expect(screen.getByText(/.+\/src\/components/)).toBeInTheDocument();
});
it('handles windows paths when truncating', () => {
const windowsPath = 'C:\\Users\\dev\\project\\src\\components';
const sessions = [createMockSession({ cwd: windowsPath })];
render(<AllSessionsView {...createDefaultProps({ sessions })} />);
expect(screen.getByText('...\\src\\components')).toBeInTheDocument();
});
it('truncates paths with only two components properly', () => {
const twoPartPath = 'a'.repeat(50); // Very long single-part path
const sessions = [createMockSession({ cwd: twoPartPath })];

View File

@@ -429,6 +429,20 @@ describe('ResponseViewer', () => {
expect(highlighter).toHaveAttribute('data-language', 'bash');
});
it('handles languages with non-word characters (c++)', () => {
const textWithCode = '```c++\nint main() { return 0; }\n```';
render(
<ResponseViewer
isOpen={true}
response={createMockResponse({ text: textWithCode })}
onClose={vi.fn()}
/>
);
const highlighter = screen.getByTestId('syntax-highlighter');
expect(highlighter).toHaveAttribute('data-language', 'cpp');
expect(screen.getByText('cpp')).toBeInTheDocument();
});
it('handles empty code blocks gracefully', () => {
const textWithEmptyBlock = 'Text before\n```\n \n```\nText after';
render(

View File

@@ -270,7 +270,7 @@ export function AboutModal({ theme, sessions, autoRunStats, onClose, onOpenLeade
{(totalActiveTimeMs > 0 || globalStats.hasCostData) && (
<div className="flex justify-between col-span-2 pt-2 border-t" style={{ borderColor: theme.colors.border }}>
{totalActiveTimeMs > 0 && (
<span style={{ color: theme.colors.textDim }}>{formatDuration(totalActiveTimeMs)}</span>
<span style={{ color: theme.colors.textDim }}>Hands-on Time: {formatDuration(totalActiveTimeMs)}</span>
)}
{!totalActiveTimeMs && globalStats.hasCostData && (
<span style={{ color: theme.colors.textDim }}>Total Cost</span>

View File

@@ -479,7 +479,7 @@ export default function MobileApp() {
// On mobile browsers, ensure the document is fully loaded before connecting
// to avoid race conditions with __MAESTRO_CONFIG__ injection
useEffect(() => {
let timeoutId: ReturnType<typeof window.setTimeout> | null = null;
let timeoutId: number | null = null;
let cancelled = false;
const scheduleAttempt = (delay: number) => {

View File

@@ -17,6 +17,7 @@
import React from 'react';
import { useThemeColors } from '../components/ThemeProvider';
import type { InputMode } from './CommandInputBar';
import { triggerHaptic } from './constants';
/** Minimum touch target size per Apple HIG guidelines (44pt) */
const MIN_TOUCH_TARGET = 44;
@@ -24,24 +25,6 @@ const MIN_TOUCH_TARGET = 44;
/** Default minimum height for the buttons */
const MIN_INPUT_HEIGHT = 48;
/**
* Trigger haptic feedback using the Vibration API
*/
function triggerHapticFeedback(pattern: 'light' | 'medium' | 'strong' | number = 'medium'): void {
if (typeof navigator !== 'undefined' && 'vibrate' in navigator) {
const duration =
pattern === 'light' ? 10 :
pattern === 'medium' ? 25 :
pattern === 'strong' ? 50 :
pattern;
try {
navigator.vibrate(duration);
} catch {
// Silently fail if vibration is not allowed
}
}
}
/**
* Common base styles for all input bar buttons
*/
@@ -88,7 +71,7 @@ export function InputModeToggleButton({
const isAiMode = inputMode === 'ai';
const handleClick = () => {
triggerHapticFeedback('light');
triggerHaptic(10);
onModeToggle();
};
@@ -334,7 +317,7 @@ export function SendInterruptButton({
const colors = useThemeColors();
const handleInterrupt = () => {
triggerHapticFeedback('strong');
triggerHaptic(50);
onInterrupt();
};
@@ -444,7 +427,7 @@ export function ExpandedModeSendInterruptButton({
const colors = useThemeColors();
const handleInterrupt = () => {
triggerHapticFeedback('strong');
triggerHaptic(50);
onInterrupt();
};
@@ -530,11 +513,3 @@ export function ExpandedModeSendInterruptButton({
</button>
);
}
export default {
InputModeToggleButton,
VoiceInputButton,
SlashCommandButton,
SendInterruptButton,
ExpandedModeSendInterruptButton,
};

View File

@@ -84,7 +84,7 @@ export function isMobileViewport(): boolean {
*/
export function supportsHaptics(): boolean {
if (typeof window === 'undefined') return false;
return 'vibrate' in navigator;
return typeof navigator.vibrate === 'function';
}
/**