mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
## 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:
@@ -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 })];
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user