mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-25 08:32:11 +03:00
Beginner UX: Essentials sidebar preset (#11286)
Validated on a 3-PR combined board: sidebar-customization + sidebar-essentials-static 30/30 combined (across the board's 4 focused files), typecheck:core + dashboard-typecheck clean, gates within baseline. New Essentials sidebar preset gives first-time users a short beginner path while advanced tools stay reachable via Command Palette search. Thank you @ignamiranda!
This commit is contained in:
1
changelog.d/features/11286-essentials-sidebar-preset.md
Normal file
1
changelog.d/features/11286-essentials-sidebar-preset.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(dashboard):** add an Essentials sidebar preset that shows only the beginner core path (Home → Endpoints → API Keys → Providers → Health → Settings) while keeping Advanced tools reachable via Command Palette search ([#11286](https://github.com/diegosouzapw/OmniRoute/pull/11286))
|
||||
@@ -521,6 +521,7 @@ export default function SidebarTab() {
|
||||
|
||||
const presetLabels: Record<SidebarPresetId, string> = {
|
||||
all: getSettingsLabel("presetAll", "All"),
|
||||
essentials: getSettingsLabel("presetEssentials", "Essentials"),
|
||||
minimal: getSettingsLabel("presetMinimal", "Minimal"),
|
||||
developer: getSettingsLabel("presetDeveloper", "Developer"),
|
||||
admin: getSettingsLabel("presetAdmin", "Admin"),
|
||||
@@ -528,6 +529,10 @@ export default function SidebarTab() {
|
||||
|
||||
const presetDescriptions: Record<SidebarPresetId, string> = {
|
||||
all: getSettingsLabel("presetAllDesc", "Show everything"),
|
||||
essentials: getSettingsLabel(
|
||||
"presetEssentialsDesc",
|
||||
"Beginner path — Advanced tools stay searchable"
|
||||
),
|
||||
minimal: getSettingsLabel("presetMinimalDesc", "Core pages only"),
|
||||
developer: getSettingsLabel("presetDeveloperDesc", "Dev & proxy tools"),
|
||||
admin: getSettingsLabel("presetAdminDesc", "Monitoring & audit"),
|
||||
|
||||
@@ -6707,6 +6707,18 @@
|
||||
"sidebarVisibility": "Hide sidebar items",
|
||||
"sidebarVisibilityDesc": "Hide any sidebar navigation entry to reduce visual clutter without disabling any features",
|
||||
"sidebarVisibilityHint": "Any sidebar section is hidden automatically when all of its entries are hidden",
|
||||
"presetAll": "All",
|
||||
"presetAllDesc": "Show everything",
|
||||
"presetEssentials": "Essentials",
|
||||
"presetEssentialsDesc": "Beginner path - Advanced tools stay searchable",
|
||||
"presetMinimal": "Minimal",
|
||||
"presetMinimalDesc": "Core pages only",
|
||||
"presetDeveloper": "Developer",
|
||||
"presetDeveloperDesc": "Dev & proxy tools",
|
||||
"presetAdmin": "Admin",
|
||||
"presetAdminDesc": "Monitoring & audit",
|
||||
"settingsSidebarTitle": "Sidebar Customization",
|
||||
"settingsSidebarDesc": "Choose which sidebar items to show. Essentials keeps Advanced tools searchable.",
|
||||
"hideHealthLogs": "Hide Health Check Logs",
|
||||
"hideHealthLogsDesc": "When ON, suppress [HealthCheck] messages in server console",
|
||||
"themeAccent": "Theme color",
|
||||
|
||||
@@ -6,8 +6,11 @@ import { useTranslations } from "next-intl";
|
||||
import {
|
||||
SIDEBAR_SECTIONS,
|
||||
HIDDEN_SIDEBAR_ITEMS_SETTING_KEY,
|
||||
SIDEBAR_PRESET_KEY,
|
||||
ESSENTIALS_ADVANCED_TOOL_IDS,
|
||||
normalizeHiddenSidebarItems,
|
||||
resolveRuntimeSidebarSections,
|
||||
type HideableSidebarItemId,
|
||||
type SidebarItemDefinition,
|
||||
type SidebarSectionChild,
|
||||
} from "@/shared/constants/sidebarVisibility";
|
||||
@@ -61,6 +64,7 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const [hiddenItems, setHiddenItems] = useState<Set<string>>(new Set());
|
||||
const [activePreset, setActivePreset] = useState<string | null>(null);
|
||||
const [radarAdminUrl, setRadarAdminUrl] = useState<unknown>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,6 +75,9 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
setHiddenItems(
|
||||
new Set(normalizeHiddenSidebarItems(data?.[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]))
|
||||
);
|
||||
setActivePreset(
|
||||
typeof data?.[SIDEBAR_PRESET_KEY] === "string" ? data[SIDEBAR_PRESET_KEY] : null
|
||||
);
|
||||
setRadarAdminUrl(data?.radarAdminUrl ?? null);
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -104,7 +111,13 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
if (isSidebarGroup(child)) {
|
||||
const subgroupLabel = safeTranslate(child.titleKey, child.titleFallback);
|
||||
return child.items
|
||||
.filter((item) => !hiddenItems.has(item.id))
|
||||
.filter((item) => {
|
||||
if (!hiddenItems.has(item.id)) return true;
|
||||
return (
|
||||
activePreset === "essentials" &&
|
||||
ESSENTIALS_ADVANCED_TOOL_IDS.has(item.id as HideableSidebarItemId)
|
||||
);
|
||||
})
|
||||
.map<PaletteItem>((item) => ({
|
||||
id: item.id,
|
||||
href: item.href,
|
||||
@@ -121,7 +134,12 @@ function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
}));
|
||||
}
|
||||
const item = child as SidebarItemDefinition;
|
||||
if (hiddenItems.has(item.id)) return [];
|
||||
if (hiddenItems.has(item.id)) {
|
||||
const keepForEssentials =
|
||||
activePreset === "essentials" &&
|
||||
ESSENTIALS_ADVANCED_TOOL_IDS.has(item.id as HideableSidebarItemId);
|
||||
if (!keepForEssentials) return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: item.id,
|
||||
|
||||
@@ -202,6 +202,36 @@ export const SIDEBAR_ITEM_ORDER_KEY = "sidebarItemOrder";
|
||||
export const SIDEBAR_PRESET_KEY = "sidebarActivePreset";
|
||||
export const SIDEBAR_SETTINGS_UPDATED_EVENT = "omniroute:settings-updated";
|
||||
|
||||
/** Beginner Essentials: core path only. Advanced tools stay reachable via search. */
|
||||
const ESSENTIALS_SHOWN: ReadonlySet<HideableSidebarItemId> = new Set([
|
||||
"home",
|
||||
"endpoints",
|
||||
"api-manager",
|
||||
"providers",
|
||||
"health",
|
||||
"settings-general",
|
||||
"settings-sidebar",
|
||||
]);
|
||||
|
||||
/** Hidden in Essentials sidebar but kept searchable in Command Palette. */
|
||||
export const ESSENTIALS_ADVANCED_TOOL_IDS: ReadonlySet<HideableSidebarItemId> = new Set([
|
||||
"playground",
|
||||
"logs",
|
||||
"batch",
|
||||
"translator",
|
||||
"combos",
|
||||
"quota",
|
||||
"analytics",
|
||||
"costs",
|
||||
"cache",
|
||||
"runtime",
|
||||
"resilience-connections",
|
||||
"mcp",
|
||||
"a2a",
|
||||
"memory",
|
||||
"skills",
|
||||
]);
|
||||
|
||||
const MINIMAL_SHOWN: ReadonlySet<HideableSidebarItemId> = new Set([
|
||||
"home",
|
||||
"endpoints",
|
||||
@@ -297,6 +327,7 @@ function buildHiddenList(shown: ReadonlySet<HideableSidebarItemId>): HideableSid
|
||||
|
||||
export const SIDEBAR_PRESETS: readonly SidebarPresetDefinition[] = [
|
||||
{ id: "all", icon: "select_all", hiddenItems: [] },
|
||||
{ id: "essentials", icon: "star", hiddenItems: buildHiddenList(ESSENTIALS_SHOWN) },
|
||||
{ id: "minimal", icon: "minimize", hiddenItems: buildHiddenList(MINIMAL_SHOWN) },
|
||||
{ id: "developer", icon: "code", hiddenItems: buildHiddenList(DEVELOPER_SHOWN) },
|
||||
{ id: "admin", icon: "admin_panel_settings", hiddenItems: buildHiddenList(ADMIN_SHOWN) },
|
||||
|
||||
@@ -174,7 +174,7 @@ export interface SidebarSectionDefinition {
|
||||
defaultPinned?: boolean;
|
||||
}
|
||||
|
||||
export type SidebarPresetId = "all" | "minimal" | "developer" | "admin";
|
||||
export type SidebarPresetId = "all" | "essentials" | "minimal" | "developer" | "admin";
|
||||
|
||||
export interface SidebarPresetDefinition {
|
||||
id: SidebarPresetId;
|
||||
|
||||
@@ -199,7 +199,10 @@ export const updateSettingsSchema = z.object({
|
||||
.array(z.enum(SIDEBAR_SECTIONS.map((s) => s.id) as [string, ...string[]]))
|
||||
.optional(),
|
||||
sidebarItemOrder: z.record(z.string(), z.array(z.string().max(100))).optional(),
|
||||
sidebarActivePreset: z.enum(["all", "minimal", "developer", "admin"]).nullable().optional(),
|
||||
sidebarActivePreset: z
|
||||
.enum(["all", "essentials", "minimal", "developer", "admin"])
|
||||
.nullable()
|
||||
.optional(),
|
||||
comboConfigMode: z.enum(COMBO_CONFIG_MODES).optional(),
|
||||
codexServiceTier: z
|
||||
.object({
|
||||
|
||||
@@ -89,9 +89,10 @@ test("applyItemOrder ignores unknown IDs in order list", () => {
|
||||
|
||||
// ─── SIDEBAR_PRESETS ──────────────────────────────────────────────────────────
|
||||
|
||||
test("SIDEBAR_PRESETS contains all four preset IDs", () => {
|
||||
test("SIDEBAR_PRESETS contains all five preset IDs", () => {
|
||||
const ids = SIDEBAR_PRESETS.map((p) => p.id);
|
||||
assert.ok(ids.includes("all"), "expected 'all' preset");
|
||||
assert.ok(ids.includes("essentials"), "expected 'essentials' preset");
|
||||
assert.ok(ids.includes("minimal"), "expected 'minimal' preset");
|
||||
assert.ok(ids.includes("developer"), "expected 'developer' preset");
|
||||
assert.ok(ids.includes("admin"), "expected 'admin' preset");
|
||||
@@ -112,6 +113,30 @@ test("SIDEBAR_PRESETS 'all' preset has no hidden items", () => {
|
||||
assert.deepEqual(allPreset.hiddenItems, []);
|
||||
});
|
||||
|
||||
test("SIDEBAR_PRESETS includes essentials as the beginner path", () => {
|
||||
assert.equal(SIDEBAR_PRESETS.length, 5);
|
||||
assert.deepEqual(
|
||||
SIDEBAR_PRESETS.map((p) => p.id),
|
||||
["all", "essentials", "minimal", "developer", "admin"]
|
||||
);
|
||||
const essentials = SIDEBAR_PRESETS.find((p) => p.id === "essentials");
|
||||
assert.ok(essentials, "expected 'essentials' preset to exist");
|
||||
const hidden = new Set(essentials.hiddenItems);
|
||||
for (const id of [
|
||||
"home",
|
||||
"endpoints",
|
||||
"api-manager",
|
||||
"providers",
|
||||
"health",
|
||||
"settings-general",
|
||||
"settings-sidebar",
|
||||
]) {
|
||||
assert.equal(hidden.has(id as never), false, `${id} should stay visible in essentials`);
|
||||
}
|
||||
assert.equal(hidden.has("playground"), true);
|
||||
assert.equal(hidden.has("logs"), true);
|
||||
});
|
||||
|
||||
test("SIDEBAR_PRESETS non-all presets have at least one hidden item", () => {
|
||||
for (const preset of SIDEBAR_PRESETS.filter((p) => p.id !== "all")) {
|
||||
assert.ok(preset.hiddenItems.length > 0, `Preset '${preset.id}' should hide at least one item`);
|
||||
|
||||
45
tests/unit/sidebar-essentials-static.test.ts
Normal file
45
tests/unit/sidebar-essentials-static.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
|
||||
test("essentials preset is registered in sidebar visibility types and presets", () => {
|
||||
const types = fs.readFileSync(
|
||||
path.join(repoRoot, "src/shared/constants/sidebarVisibility/types.ts"),
|
||||
"utf8"
|
||||
);
|
||||
const visibility = fs.readFileSync(
|
||||
path.join(repoRoot, "src/shared/constants/sidebarVisibility.ts"),
|
||||
"utf8"
|
||||
);
|
||||
const schema = fs.readFileSync(
|
||||
path.join(repoRoot, "src/shared/validation/settingsSchemas.ts"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
assert.match(types, /"essentials"/);
|
||||
assert.match(visibility, /id:\s*"essentials"/);
|
||||
assert.match(visibility, /ESSENTIALS_ADVANCED_TOOL_IDS/);
|
||||
assert.match(schema, /"essentials"/);
|
||||
});
|
||||
|
||||
test("command palette keeps essentials advanced tools searchable", () => {
|
||||
const source = fs.readFileSync(
|
||||
path.join(repoRoot, "src/shared/components/CommandPalette.tsx"),
|
||||
"utf8"
|
||||
);
|
||||
assert.match(source, /ESSENTIALS_ADVANCED_TOOL_IDS/);
|
||||
assert.match(source, /activePreset === "essentials"/);
|
||||
});
|
||||
|
||||
test("essentials i18n keys exist in en.json", () => {
|
||||
const en = JSON.parse(
|
||||
fs.readFileSync(path.join(repoRoot, "src/i18n/messages/en.json"), "utf8")
|
||||
) as { settings: Record<string, string> };
|
||||
assert.equal(en.settings.presetEssentials, "Essentials");
|
||||
assert.match(en.settings.presetEssentialsDesc, /Beginner path/i);
|
||||
assert.match(en.settings.presetEssentialsDesc, /searchable/i);
|
||||
});
|
||||
Reference in New Issue
Block a user