feat: convert serial console into the serial plugin

This commit is contained in:
LukeGus committed 2026-09-23 23:06:37 -05:00
1 parent 708eedb861
commit 791d8656a1
121 files changed
+2216 -1425

No files matched your search

+10 -5
View File
@@ -30,17 +30,22 @@ import {
Share2,
} from "lucide-react";
import { tabIcon } from "@/shell/tabUtils";
import { isSessionTabType } from "@/shell/tab-registry";
import { isSessionTabType, isShareableTabType } from "@/shell/tab-registry";
import { isElectron } from "@/lib/electron";
import type { Tab, TabType, SplitMode } from "@/types/ui-types";
import { SPLIT_MODES, PANE_COUNTS } from "@/lib/theme";
/**
* Tabs holding a live connection that can be refreshed and shared: the
* registered session tabs. Serial sessions are not shareable.
* Tabs holding a live connection that can be refreshed: the registered
* session tabs.
*/
function isConnectionTab(type: TabType): boolean {
return type !== "serial" && isSessionTabType(type);
return isSessionTabType(type);
}
/** Connection tabs that also offer "Share session". */
function isShareableConnectionTab(type: TabType): boolean {
return isConnectionTab(type) && isShareableTabType(type);
}
export function TabBar({
@@ -467,7 +472,7 @@ export function TabBar({
<RefreshCw className="size-3" />
</button>
)}
{isConnectionTab(tab.type) && onOpenShare && (
{isShareableConnectionTab(tab.type) && onOpenShare && (
<button
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => {
+2 -12
View File
@@ -19,7 +19,6 @@ import { getTabType } from "./tab-registry";
* not a meaningful part of an arrangement.
*/
const CORE_CAPTURABLE = new Set([
"serial",
"homepage",
"termix-id",
"session-logs",
@@ -52,10 +51,7 @@ const CORE_UNSAVED = new Set([
*/
export function isUnregisteredPluginTabType(type: string): boolean {
return (
!CORE_UNSAVED.has(type) &&
!CORE_CAPTURABLE.has(type) &&
type !== "serial" &&
!getTabType(type)
!CORE_UNSAVED.has(type) && !CORE_CAPTURABLE.has(type) && !getTabType(type)
);
}
@@ -94,14 +90,13 @@ export function buildLayoutTabSnapshots(
const snapshots: WorkspaceTabSnapshot[] = capturable.map((tab) => ({
slotId: slotIdByTabId.get(tab.id)!,
type: tab.type,
hostSyncId: tab.type === "serial" ? null : (tab.host?.syncId ?? null),
hostSyncId: tab.host?.syncId ?? null,
hostNameSnapshot: tab.host?.name ?? null,
label: tab.label,
customLabel: tab.customLabel,
initialFilePath: tab.initialFilePath,
initialPath: tab.initialPath,
data: tab.data,
serialConfig: tab.type === "serial" ? tab.serialConfig : undefined,
}));
return { snapshots, slotIdByTabId };
@@ -139,14 +134,9 @@ export function resolveLayoutTabTarget(
snapshot: WorkspaceTabSnapshot,
allHosts: Host[],
):
| { kind: "serial" }
| { kind: "singleton"; host?: Host }
| { kind: "host"; host: Host }
| { kind: "skip" } {
if (snapshot.type === "serial") {
return snapshot.serialConfig ? { kind: "serial" } : { kind: "skip" };
}
let host: Host | undefined;
if (snapshot.hostSyncId) {
host = allHosts.find((h) => h.syncId === snapshot.hostSyncId);
+8 -1
View File
@@ -61,6 +61,8 @@ export interface TabTypeDef {
persistent?: boolean;
singleton?: boolean;
session?: boolean;
/** Whether a session tab offers "Share session". Default true. */
shareable?: boolean;
hostless?: boolean;
restore?: (host: Host) => boolean;
activityTypes?: string[];
@@ -91,7 +93,7 @@ export const resetTabTypes = registry.reset;
/** Core tab types that are saved and reopened after login. */
const CORE_PERSISTENT = new Set<string>();
/** Core tab types that hold a live session (close confirm, refresh). */
const CORE_SESSION = new Set(["serial"]);
const CORE_SESSION = new Set<string>();
/** Core tab types that restore without a host. */
const CORE_HOSTLESS = new Set(["dashboard"]);
@@ -115,6 +117,11 @@ export function isSessionTabType(type: string): boolean {
return CORE_SESSION.has(type) || !!registry.get(type)?.session;
}
/** Whether a session tab of this type offers "Share session". Default true. */
export function isShareableTabType(type: string): boolean {
return registry.get(type)?.shareable !== false;
}
export function isHostlessTabType(type: string): boolean {
return CORE_HOSTLESS.has(type) || !!registry.get(type)?.hostless;
}
-21
View File
@@ -6,7 +6,6 @@ import {
LayoutPanelLeft,
Server,
Settings,
Usb,
User,
Fingerprint,
Hammer,
@@ -15,7 +14,6 @@ import {
} from "lucide-react";
import { lazy, Suspense } from "react";
import { useTranslation } from "react-i18next";
import type { SerialHandle } from "@/features/serial/serial-types";
import type { Tab, TabType } from "@/types/ui-types";
import { hostToSSHHost } from "@/lib/host-to-ssh-host";
import { PluginViewPlaceholder } from "@/plugin-host/PluginViewPlaceholder";
@@ -46,11 +44,6 @@ const HomepageCanvas = lazy(() =>
default: m.HomepageCanvas,
})),
);
const Serial = lazy(() =>
import("@/features/serial/Serial").then((m) => ({
default: m.Serial,
})),
);
// Rail panels promoted to full tabs.
const TermixIdPanel = lazy(() =>
import("@/sidebar/TermixIdPanel").then((m) => ({ default: m.TermixIdPanel })),
@@ -134,8 +127,6 @@ export function tabIcon(type: TabType) {
return <User className="size-3.5" />;
case "admin-settings":
return <Settings className="size-3.5" />;
case "serial":
return <Usb className="size-3.5" />;
case "homepage":
return <LayoutGrid className="size-3.5" />;
case "collab":
@@ -237,18 +228,6 @@ export function renderTabContent(tab: Tab, context: TabRenderContext) {
/>,
);
case "serial":
if (!tab.serialConfig)
return <EmptyState icon={Usb} messageKey="serial.notSupportedTitle" />;
return withTabSuspense(
<Serial
ref={tab.terminalRef as React.Ref<SerialHandle>}
config={tab.serialConfig}
isVisible={isVisible}
instanceId={tab.instanceId}
/>,
);
case "homepage":
return withTabSuspense(<HomepageCanvas />);