feat: convert tmux monitoring into a plugin (B10)

This commit is contained in:
LukeGus committed 2026-09-23 22:42:56 -05:00
1 parent 79d77678c9
commit 708eedb861
145 files changed
+30308 -4458

No files matched your search

-39
View File
@@ -20,7 +20,6 @@ import {
Search,
User,
KeyRound,
Layers, // --- tmux-monitor ---
Clock,
Folder,
Pencil,
@@ -57,26 +56,6 @@ interface CommandPaletteProps {
onOpenPanel?: (view: string) => void;
}
/** Core per-host tools. Plugins add theirs as host actions. */
function getSshActions(host: Host): {
type: TabType;
icon: React.ElementType;
label: string;
}[] {
return [
// --- tmux-monitor --- opt-in per host, off by default
host.enableTmuxMonitor && {
type: "tmux_monitor",
icon: Layers,
label: "Tmux Monitor",
},
].filter(Boolean) as {
type: TabType;
icon: React.ElementType;
label: string;
}[];
}
export function CommandPalette({
isOpen,
setIsOpen,
@@ -535,24 +514,6 @@ export function CommandPalette({
</span>
</div>
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
{host.enableSsh &&
getSshActions(host).map(
({ type, icon: Icon, label }) => (
<button
key={type}
title={label}
onClick={(e) => {
e.stopPropagation();
handleAction(() =>
onOpenTab(type, host.name),
);
}}
className="flex items-center justify-center size-7 rounded text-muted-foreground/50 hover:text-foreground hover:bg-muted-foreground/10 transition-colors"
>
<Icon className="size-3.5" />
</button>
),
)}
{hostActionsFor(hostActions, host).map((action) => {
const Icon = action.icon;
const label =
+11 -7
View File
@@ -146,9 +146,12 @@ export function TabProvider({ children }: TabProviderProps) {
const addTab = useCallback(
(tabData: Omit<Tab, "id">): number => {
// --- tmux-monitor --- (tmux_monitor is a singleton tab like ssh_manager:
// re-opening focuses the existing tab instead of adding a duplicate)
if (tabData.type === "ssh_manager" || tabData.type === "tmux_monitor") {
// A singleton tab: re-opening focuses the existing tab instead of
// adding a duplicate.
if (
tabData.type === "ssh_manager" ||
getTabType(tabData.type)?.singleton
) {
const existingTab = tabs.find((t) => t.type === tabData.type);
if (existingTab) {
setTabs((prev) =>
@@ -179,11 +182,12 @@ export function TabProvider({ children }: TabProviderProps) {
const isCommandTarget = !!getTabType(tabData.type)?.commandTarget;
const needsUniqueTitle =
isCommandTarget || tabData.type === "file_manager";
const singletonTitleKey = getTabType(tabData.type)?.singleton
? getTabType(tabData.type)?.titleKey
: undefined;
const effectiveTitle = needsUniqueTitle
? computeUniqueTitle(tabData.type, tabData.title)
: tabData.type === "tmux_monitor" // --- tmux-monitor ---
? tabData.title || t("nav.tmuxMonitor")
: tabData.title || "";
: tabData.title || (singletonTitleKey ? t(singletonTitleKey) : "");
const newTab: Tab = {
...tabData,
id,
@@ -204,7 +208,7 @@ export function TabProvider({ children }: TabProviderProps) {
setAllSplitScreenTab((prev) => prev.filter((tid) => tid !== id));
return id;
},
[computeUniqueTitle, tabs, t], // --- tmux-monitor --- (added t)
[computeUniqueTitle, tabs, t],
);
const pendingCurrentTabRef = useRef<number | null>(null);
-2
View File
@@ -19,7 +19,6 @@ import { getTabType } from "./tab-registry";
* not a meaningful part of an arrangement.
*/
const CORE_CAPTURABLE = new Set([
"tmux_monitor",
"serial",
"homepage",
"termix-id",
@@ -30,7 +29,6 @@ const CORE_CAPTURABLE = new Set([
/** Core types reopened as singletons, with an optional preselected host. */
const CORE_SINGLETON = new Set([
"tmux_monitor",
"homepage",
"termix-id",
"session-logs",
+1 -24
View File
@@ -8,7 +8,6 @@ import {
Settings,
Usb,
User,
Layers, // --- tmux-monitor ---
Fingerprint,
Hammer,
ScrollText,
@@ -37,11 +36,6 @@ const CollabRoomTab = lazy(() =>
})),
);
const loadTmuxMonitor = () =>
import("@/features/tmux-monitor/TmuxMonitor").then((m) => ({
default: m.TmuxMonitor,
}));
const TmuxMonitor = lazy(loadTmuxMonitor);
const DashboardTab = lazy(() =>
import("@/dashboard/DashboardTab").then((m) => ({
default: m.DashboardTab,
@@ -73,13 +67,9 @@ const MacrosPanel = lazy(() =>
const SshToolsPanel = lazy(() =>
import("@/sidebar/SshToolsPanel").then((m) => ({ default: m.SshToolsPanel })),
);
const tabSurfaceLoaders: Partial<Record<TabType, () => Promise<unknown>>> = {
tmux_monitor: loadTmuxMonitor,
};
/** Download a likely next tab without starting a connection or mounting UI. */
export function preloadTabSurface(type: TabType): void {
const loader = tabSurfaceLoaders[type] ?? getTabType(type)?.preload;
const loader = getTabType(type)?.preload;
if (loader) runAdaptiveBackgroundTask("module", `tab:${type}`, loader);
}
@@ -144,9 +134,6 @@ export function tabIcon(type: TabType) {
return <User className="size-3.5" />;
case "admin-settings":
return <Settings className="size-3.5" />;
// --- tmux-monitor ---
case "tmux_monitor":
return <Layers className="size-3.5" />;
case "serial":
return <Usb className="size-3.5" />;
case "homepage":
@@ -239,7 +226,6 @@ export interface TabRenderContext {
export function renderTabContent(tab: Tab, context: TabRenderContext) {
const { shell, isVisible = true, isFocusedPane = true, panelProps } = context;
const { host } = tab;
switch (tab.type) {
case "dashboard":
@@ -251,15 +237,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
/>,
);
// --- tmux-monitor ---
case "tmux_monitor":
return withTabSuspense(
<TmuxMonitor
initialHostId={host ? parseInt(host.id, 10) : undefined}
isVisible={isVisible}
/>,
);
case "serial":
if (!tab.serialConfig)
return <EmptyState icon={Usb} messageKey="serial.notSupportedTitle" />;