mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
feat: add Mark as Unread option to tab context menu
Add a new menu item in the tab hover overlay that allows users to manually mark a tab as unread. This is useful for using the unread indicator as a todo/reminder system. Changes: - Add onTabMarkUnread callback prop to TabBar and MainPanel - Add "Mark as Unread" menu item with Mail icon in tab overlay - Implement handler in App.tsx that sets hasUnread: true on the tab - Add test case for the new functionality Claude ID: 4884b984-82ed-4c6d-a2ac-834040680db0 Maestro ID: b9bc0d08-5be2-4fdf-93cd-5618a8d53b35
This commit is contained in:
@@ -86,6 +86,7 @@ describe('TabBar', () => {
|
||||
const mockOnTabReorder = vi.fn();
|
||||
const mockOnCloseOthers = vi.fn();
|
||||
const mockOnTabStar = vi.fn();
|
||||
const mockOnTabMarkUnread = vi.fn();
|
||||
const mockOnToggleUnreadFilter = vi.fn();
|
||||
const mockOnOpenTabSearch = vi.fn();
|
||||
|
||||
@@ -1190,6 +1191,35 @@ describe('TabBar', () => {
|
||||
expect(mockOnRequestRename).toHaveBeenCalledWith('tab-1');
|
||||
});
|
||||
|
||||
it('calls onTabMarkUnread when Mark as Unread clicked', async () => {
|
||||
const tabs = [createTab({
|
||||
id: 'tab-1',
|
||||
name: 'Tab 1',
|
||||
claudeSessionId: 'abc123'
|
||||
})];
|
||||
|
||||
render(
|
||||
<TabBar
|
||||
tabs={tabs}
|
||||
activeTabId="tab-1"
|
||||
theme={mockTheme}
|
||||
onTabSelect={mockOnTabSelect}
|
||||
onTabClose={mockOnTabClose}
|
||||
onNewTab={mockOnNewTab}
|
||||
onTabMarkUnread={mockOnTabMarkUnread}
|
||||
/>
|
||||
);
|
||||
|
||||
const tab = screen.getByText('Tab 1').closest('[data-tab-id]')!;
|
||||
fireEvent.mouseEnter(tab);
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(450);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText('Mark as Unread'));
|
||||
expect(mockOnTabMarkUnread).toHaveBeenCalledWith('tab-1');
|
||||
});
|
||||
|
||||
it('displays session name in overlay header', async () => {
|
||||
const tabs = [createTab({
|
||||
id: 'tab-1',
|
||||
|
||||
@@ -5196,6 +5196,18 @@ export default function MaestroConsole() {
|
||||
};
|
||||
}));
|
||||
}}
|
||||
onTabMarkUnread={(tabId: string) => {
|
||||
if (!activeSession) return;
|
||||
setSessions(prev => prev.map(s => {
|
||||
if (s.id !== activeSession.id) return s;
|
||||
return {
|
||||
...s,
|
||||
aiTabs: s.aiTabs.map(t =>
|
||||
t.id === tabId ? { ...t, hasUnread: true } : t
|
||||
)
|
||||
};
|
||||
}));
|
||||
}}
|
||||
onToggleTabReadOnlyMode={() => {
|
||||
if (!activeSession) return;
|
||||
const activeTab = getActiveTab(activeSession);
|
||||
|
||||
@@ -144,6 +144,7 @@ interface MainPanelProps {
|
||||
onTabReorder?: (fromIndex: number, toIndex: number) => void;
|
||||
onCloseOtherTabs?: (tabId: string) => void;
|
||||
onTabStar?: (tabId: string, starred: boolean) => void;
|
||||
onTabMarkUnread?: (tabId: string) => void;
|
||||
onUpdateTabByClaudeSessionId?: (claudeSessionId: string, updates: { name?: string | null; starred?: boolean }) => void;
|
||||
onToggleTabReadOnlyMode?: () => void;
|
||||
onToggleTabSaveToHistory?: () => void;
|
||||
@@ -212,7 +213,7 @@ export const MainPanel = forwardRef<MainPanelHandle, MainPanelProps>(function Ma
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Extract tab handlers from props
|
||||
const { onTabSelect, onTabClose, onNewTab, onTabRename, onRequestTabRename, onTabReorder, onCloseOtherTabs, onTabStar, showUnreadOnly, onToggleUnreadFilter, onOpenTabSearch } = props;
|
||||
const { onTabSelect, onTabClose, onNewTab, onTabRename, onRequestTabRename, onTabReorder, onCloseOtherTabs, onTabStar, onTabMarkUnread, showUnreadOnly, onToggleUnreadFilter, onOpenTabSearch } = props;
|
||||
|
||||
// Get the active tab for header display
|
||||
// The header should show the active tab's data (UUID, name, cost, context), not session-level data
|
||||
@@ -848,6 +849,7 @@ export const MainPanel = forwardRef<MainPanelHandle, MainPanelProps>(function Ma
|
||||
onTabReorder={onTabReorder}
|
||||
onCloseOthers={onCloseOtherTabs}
|
||||
onTabStar={onTabStar}
|
||||
onTabMarkUnread={onTabMarkUnread}
|
||||
showUnreadOnly={showUnreadOnly}
|
||||
onToggleUnreadFilter={onToggleUnreadFilter}
|
||||
onOpenTabSearch={onOpenTabSearch}
|
||||
|
||||
@@ -15,6 +15,7 @@ interface TabBarProps {
|
||||
onTabReorder?: (fromIndex: number, toIndex: number) => void;
|
||||
onCloseOthers?: (tabId: string) => void;
|
||||
onTabStar?: (tabId: string, starred: boolean) => void;
|
||||
onTabMarkUnread?: (tabId: string) => void;
|
||||
showUnreadOnly?: boolean;
|
||||
onToggleUnreadFilter?: () => void;
|
||||
onOpenTabSearch?: () => void;
|
||||
@@ -36,6 +37,7 @@ interface TabProps {
|
||||
isDragOver: boolean;
|
||||
onRename: () => void;
|
||||
onStar?: (starred: boolean) => void;
|
||||
onMarkUnread?: () => void;
|
||||
shortcutHint?: number | null;
|
||||
registerRef?: (el: HTMLDivElement | null) => void;
|
||||
hasDraft?: boolean;
|
||||
@@ -77,6 +79,7 @@ function Tab({
|
||||
isDragOver,
|
||||
onRename,
|
||||
onStar,
|
||||
onMarkUnread,
|
||||
shortcutHint,
|
||||
registerRef,
|
||||
hasDraft
|
||||
@@ -163,6 +166,12 @@ function Tab({
|
||||
setOverlayOpen(false);
|
||||
};
|
||||
|
||||
const handleMarkUnreadClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onMarkUnread?.();
|
||||
setOverlayOpen(false);
|
||||
};
|
||||
|
||||
const displayName = getTabDisplayName(tab);
|
||||
|
||||
// Browser-style tab: all tabs have borders, active tab "connects" to content
|
||||
@@ -364,6 +373,15 @@ function Tab({
|
||||
<Edit2 className="w-3.5 h-3.5" style={{ color: theme.colors.textDim }} />
|
||||
Rename Tab
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleMarkUnreadClick}
|
||||
className="w-full flex items-center gap-2 px-2 py-1.5 rounded text-xs hover:bg-white/10 transition-colors"
|
||||
style={{ color: theme.colors.textMain }}
|
||||
>
|
||||
<Mail className="w-3.5 h-3.5" style={{ color: theme.colors.textDim }} />
|
||||
Mark as Unread
|
||||
</button>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
@@ -389,6 +407,7 @@ export function TabBar({
|
||||
onTabReorder,
|
||||
onCloseOthers,
|
||||
onTabStar,
|
||||
onTabMarkUnread,
|
||||
showUnreadOnly: showUnreadOnlyProp,
|
||||
onToggleUnreadFilter,
|
||||
onOpenTabSearch
|
||||
@@ -585,6 +604,7 @@ export function TabBar({
|
||||
isDragOver={dragOverTabId === tab.id}
|
||||
onRename={() => handleRenameRequest(tab.id)}
|
||||
onStar={onTabStar ? (starred) => onTabStar(tab.id, starred) : undefined}
|
||||
onMarkUnread={onTabMarkUnread ? () => onTabMarkUnread(tab.id) : undefined}
|
||||
shortcutHint={!showUnreadOnly && originalIndex < 9 ? originalIndex + 1 : null}
|
||||
hasDraft={hasDraft(tab)}
|
||||
registerRef={(el) => {
|
||||
|
||||
Reference in New Issue
Block a user