feat: dev:all runner for web, electron and docker with plugin hot reload, termix-plugin dev

This commit is contained in:
LukeGus committed 2026-10-06 15:55:00 -05:00
1 parent 49fc241e06
commit ec80a8ef8d
24 files changed
+1848 -44

No files matched your search

+5
View File
@@ -398,6 +398,11 @@ function onPluginsChanged() {
void syncPlugins();
}
// Sent by scripts/dev.mjs after it rebuilt a plugin or restarted the backend.
import.meta.hot?.on("termix:plugins-reloaded", () => {
if (started) void syncPlugins();
});
function onFocus() {
const now = Date.now();
if (now - lastFocusSync < FOCUS_SYNC_INTERVAL_MS) return;
+11 -8
View File
@@ -19,6 +19,7 @@ import {
import { useHiddenRailTabs } from "@/sidebar/hidden-rail-tabs";
import { RailBadge } from "@/sidebar/RailBadge";
import { cn } from "@/lib/utils";
import { isElectron } from "@/lib/electron";
const PRIMARY_COUNT = 4;
@@ -183,14 +184,16 @@ export function MobileBar({
onOpenSettings();
}}
/>
<SheetRow
icon={LogOut}
label={t("common.logout")}
onClick={() => {
close();
onLogout();
}}
/>
{!isElectron() && (
<SheetRow
icon={LogOut}
label={t("common.logout")}
onClick={() => {
close();
onLogout();
}}
/>
)}
</div>
</div>
</div>
+20 -14
View File
@@ -25,6 +25,7 @@ import { useUiPreferencesContext } from "@/contexts/UiPreferencesContext";
import { toggleHiddenRailTab, useHiddenRailTabs } from "./hidden-rail-tabs";
import { RailBadge } from "./RailBadge";
import { rem } from "@/lib/rem";
import { isElectron } from "@/lib/electron";
/** Core rail views; plugins add their own ids at runtime. */
export type CoreRailView =
@@ -437,20 +438,25 @@ export function AppRail({
</>
)}
{footerItems.map(renderItem)}
{footerItems.length > 0 && <Rule expanded={railExpanded} />}
<button
type="button"
onClick={() => onLogout({ manual: true })}
style={btnStyle}
title={t("common.logout")}
aria-label={t("common.logout")}
className={`${btnBase} text-muted-foreground hover:text-destructive hover:bg-destructive/10`}
>
<RailIcon>
<LogOut size={16} />
</RailIcon>
<RailLabel expanded={railExpanded}>{t("common.logout")}</RailLabel>
</button>
{footerItems.length > 0 && !isElectron() && (
<Rule expanded={railExpanded} />
)}
{/* The desktop signs in to its own local profile by itself. */}
{!isElectron() && (
<button
type="button"
onClick={() => onLogout({ manual: true })}
style={btnStyle}
title={t("common.logout")}
aria-label={t("common.logout")}
className={`${btnBase} text-muted-foreground hover:text-destructive hover:bg-destructive/10`}
>
<RailIcon>
<LogOut size={16} />
</RailIcon>
<RailLabel expanded={railExpanded}>{t("common.logout")}</RailLabel>
</button>
)}
</div>
<div className="shrink-0 border-t border-border">
+12
View File
@@ -24,6 +24,7 @@ afterEach(() => {
cleanup();
resetRegisteredRailItems();
localStorage.clear();
delete (window as { IS_ELECTRON?: boolean }).IS_ELECTRON;
});
function renderBar(onRailClick = vi.fn()) {
@@ -77,6 +78,17 @@ describe("MobileBar", () => {
expect(screen.getByText("nav.group.objects")).toBeTruthy();
});
it("leaves logout out of More on the desktop", () => {
renderBar();
fireEvent.click(screen.getByLabelText("common.more"));
expect(screen.getByText("common.logout")).toBeTruthy();
cleanup();
(window as { IS_ELECTRON?: boolean }).IS_ELECTRON = true;
renderBar();
fireEvent.click(screen.getByLabelText("common.more"));
expect(screen.queryByText("common.logout")).toBeNull();
});
it("leaves out hidden items", () => {
localStorage.setItem("hiddenRailTabs", JSON.stringify(["hosts"]));
renderBar();
+11 -1
View File
@@ -1,5 +1,5 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Bell, Boxes } from "lucide-react";
import { AppRail } from "../../sidebar/AppRail";
@@ -25,6 +25,7 @@ vi.mock("@/hooks/use-permissions", () => ({
afterEach(() => {
resetRegisteredRailItems();
localStorage.clear();
delete (window as { IS_ELECTRON?: boolean }).IS_ELECTRON;
});
function renderRail(onRailClick = vi.fn()) {
@@ -44,6 +45,15 @@ function renderRail(onRailClick = vi.fn()) {
}
describe("AppRail", () => {
it("shows logout in the browser but not on the desktop", () => {
renderRail();
expect(screen.getByLabelText("common.logout")).toBeTruthy();
cleanup();
(window as { IS_ELECTRON?: boolean }).IS_ELECTRON = true;
renderRail();
expect(screen.queryByLabelText("common.logout")).toBeNull();
});
it("puts a footer item above the profile with its badge", async () => {
registerRailItem({
id: "inbox",