mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
b65daf6845
* feat: add notification suppression rules * fix: restore label routing and routing test mocks for suppression * fix: allow bell mute shortcuts for history-only notification categories Suppression rule validation used the routable category whitelist, which rejected history-only categories such as update_started that appear in the bell during stack updates. * feat: expand Mute Rules UX with compose-first entry points and activity badges * fix: add missing NodeContext mocks for notification suppression tests
104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
import { useMemo } from 'react';
|
||
import {
|
||
Activity,
|
||
ArrowUpRight,
|
||
BellOff,
|
||
BellRing,
|
||
CalendarClock,
|
||
Download,
|
||
Pin,
|
||
PinOff,
|
||
Play,
|
||
RefreshCw,
|
||
RotateCw,
|
||
Square,
|
||
Tag,
|
||
Trash2,
|
||
} from 'lucide-react';
|
||
import type { MenuGroup, MenuItem, StackMenuCtx } from '@/components/sidebar/sidebar-types';
|
||
|
||
export function useStackMenuItems(_file: string, ctx: StackMenuCtx): MenuGroup[] {
|
||
const {
|
||
stackStatus, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||
deploy, stop, restart, update, remove, pin, unpin, toggleLabel,
|
||
menuVisibility, openScheduleTask,
|
||
canMuteNotifications, muteStackAll, muteStackDeploySuccess, muteStackMonitor, openStackMuteRules,
|
||
} = ctx;
|
||
const { showDeploy, showStop, showRestart, showUpdate } = menuVisibility;
|
||
|
||
return useMemo(() => {
|
||
const groups: MenuGroup[] = [];
|
||
|
||
const inspect: MenuItem[] = [
|
||
{ id: 'alerts', label: 'Alerts', icon: BellRing, shortcut: 'A', onSelect: openAlertSheet },
|
||
{ id: 'auto-heal', label: 'Auto-Heal', icon: Activity, shortcut: 'H', onSelect: openAutoHeal },
|
||
];
|
||
inspect.push({ id: 'check-updates', label: 'Check updates', icon: RefreshCw, shortcut: 'U', onSelect: checkUpdates });
|
||
if (stackStatus === 'running' && canOpenApp) {
|
||
inspect.push({ id: 'open-app', label: 'Open App', icon: ArrowUpRight, shortcut: '↗', onSelect: openStackApp });
|
||
}
|
||
if (canMuteNotifications) {
|
||
inspect.push({
|
||
id: 'mute',
|
||
label: 'Mute',
|
||
icon: BellOff,
|
||
onSelect: () => {},
|
||
subItems: [
|
||
{ id: 'mute-stack-all', label: 'Mute notifications for this stack', icon: BellOff, onSelect: muteStackAll },
|
||
{ id: 'mute-stack-deploy', label: 'Mute deploy success noise', icon: BellOff, onSelect: muteStackDeploySuccess },
|
||
{ id: 'mute-stack-monitor', label: 'Mute monitor alerts for this stack', icon: BellOff, onSelect: muteStackMonitor },
|
||
{ id: 'mute-stack-manage', label: 'Manage stack mute rules', icon: BellOff, onSelect: openStackMuteRules },
|
||
],
|
||
});
|
||
}
|
||
groups.push({ id: 'inspect', items: inspect });
|
||
|
||
const organize: MenuItem[] = [];
|
||
if (canEditLabels) {
|
||
organize.push({
|
||
id: 'labels',
|
||
label: 'Labels',
|
||
icon: Tag,
|
||
shortcut: 'L ›',
|
||
onSelect: () => {},
|
||
subItems: labels.map(l => ({
|
||
id: `label:${l.id}`,
|
||
label: l.name,
|
||
icon: Tag,
|
||
onSelect: () => toggleLabel(l.id),
|
||
})),
|
||
});
|
||
}
|
||
organize.push(
|
||
isPinned
|
||
? { id: 'pin', label: 'Unpin', icon: PinOff, shortcut: 'P', onSelect: unpin }
|
||
: { id: 'pin', label: 'Pin to top', icon: Pin, shortcut: 'P', onSelect: pin }
|
||
);
|
||
groups.push({ id: 'organize', items: organize });
|
||
|
||
const lifecycle: MenuItem[] = [];
|
||
if (showDeploy) lifecycle.push({ id: 'deploy', label: 'Deploy', icon: Play, shortcut: '⌘↵', onSelect: deploy, disabled: isBusy });
|
||
if (showStop) lifecycle.push({ id: 'stop', label: 'Stop', icon: Square, shortcut: '⌘.', onSelect: stop, disabled: isBusy });
|
||
if (showRestart) lifecycle.push({ id: 'restart', label: 'Restart', icon: RotateCw, shortcut: '⌘R', onSelect: restart, disabled: isBusy });
|
||
if (showUpdate) lifecycle.push({ id: 'update', label: 'Update', icon: Download, shortcut: '⌘↑', onSelect: update, disabled: isBusy });
|
||
if (isAdmin) lifecycle.push({ id: 'schedule', label: 'Schedule task', icon: CalendarClock, onSelect: openScheduleTask });
|
||
if (lifecycle.length > 0) groups.push({ id: 'lifecycle', items: lifecycle });
|
||
|
||
if (canDelete) {
|
||
groups.push({
|
||
id: 'destructive',
|
||
items: [{ id: 'delete', label: 'Delete', icon: Trash2, shortcut: '⌘⌫', destructive: true, onSelect: remove }],
|
||
});
|
||
}
|
||
|
||
return groups;
|
||
}, [
|
||
stackStatus, canOpenApp, isBusy, isAdmin, canDelete, canEditLabels, isPinned, labels,
|
||
showDeploy, showStop, showRestart, showUpdate,
|
||
openAlertSheet, openAutoHeal, checkUpdates, openStackApp,
|
||
deploy, stop, restart, update, remove, pin, unpin, toggleLabel, openScheduleTask,
|
||
canMuteNotifications, muteStackAll, muteStackDeploySuccess, muteStackMonitor, openStackMuteRules,
|
||
]);
|
||
}
|