feat: rebuild workspaces as the reference plugin

This commit is contained in:
LukeGus committed 2026-09-23 07:04:03 -05:00
1 parent d774df8b41
commit f217d8db89
79 files changed
+32995 -2184

No files matched your search

+11 -5
View File
@@ -13,7 +13,7 @@ import {
import type { SplitMode, TabType, ToolsTab } from "@/types/ui-types";
import { isElectron } from "@/lib/electron";
import { readRailPreference, setRailPreference } from "./rail-preferences";
import { useRailItems, visibleRailItems } from "./rail-items";
import { useRailItems, type RailItemDef } from "./rail-items";
/** Core rail views; plugins add their own ids at runtime. */
export type CoreRailView =
@@ -52,12 +52,13 @@ type RailItem =
| { kind: "separator" };
function buildRailButtons(
items: RailItemDef[],
splitMode: SplitMode,
t: (key: string) => string,
hidden: Set<string>,
): RailItem[] {
const all: RailItem[] = [];
for (const item of visibleRailItems()) {
for (const item of items) {
const Icon = item.icon;
if (item.kind === "tab") {
all.push({
@@ -192,8 +193,8 @@ export function AppRail({
};
}, [menuPos]);
// Plugins add and remove rail items at runtime.
useRailItems();
// Plugins add and remove rail items at runtime, and permissions hide some.
const railItems = useRailItems();
useEffect(() => {
const handler = () => {
@@ -246,7 +247,12 @@ export function AppRail({
...hiddenTabs,
...(isRemoteSyncConnected ? [] : ["termix-id"]),
]);
const railButtons = buildRailButtons(splitMode, t, effectiveHiddenTabs);
const railButtons = buildRailButtons(
railItems,
splitMode,
t,
effectiveHiddenTabs,
);
const setRailPinned = (nextPinned: boolean) => {
setPinned(nextPinned);
localStorage.setItem("pinAppRail", String(nextPinned));
+30 -3
View File
@@ -19,7 +19,8 @@ import {
Presentation,
type LucideIcon,
} from "lucide-react";
import { useSyncExternalStore } from "react";
import { useMemo, useSyncExternalStore } from "react";
import { usePermissions } from "@/hooks/use-permissions";
import { isElectron } from "@/lib/electron";
import { createRegistry } from "@/lib/registry";
@@ -69,6 +70,8 @@ export interface RailItemDef {
order?: number;
/** Set for items a plugin registered. */
pluginId?: string;
/** Role permission the user needs to see it at all, as a full id. */
permission?: string;
}
export const RAIL_ITEMS: RailItemDef[] = [
@@ -250,13 +253,37 @@ function railItemsSnapshot(): RailItemDef[] {
return railSnapshot;
}
/** visibleRailItems() as a hook, re-rendering when plugins change it. */
/**
* Drops items gated on a permission the user lacks. Until permissions load,
* gated items stay hidden rather than flashing in and out.
*/
export function permittedRailItems(
items: RailItemDef[],
permissions: { has: (permission: string) => boolean; loaded: boolean },
): RailItemDef[] {
if (!items.some((item) => item.permission)) return items;
return items.filter(
(item) =>
!item.permission ||
(permissions.loaded && permissions.has(item.permission)),
);
}
/**
* visibleRailItems() as a hook, re-rendering when plugins change it, without
* the items the user's permissions hide.
*/
export function useRailItems(): RailItemDef[] {
return useSyncExternalStore(
const items = useSyncExternalStore(
registeredRailItems.subscribe,
railItemsSnapshot,
railItemsSnapshot,
);
const { has, loaded } = usePermissions();
return useMemo(
() => permittedRailItems(items, { has, loaded }),
[items, has, loaded],
);
}
/**