Skip to content

[codex] Add full sidebar visibility controls - #739

Merged
diegosouzapw merged 1 commit into
diegosouzapw:mainfrom
rdself:coder/sidebar-visibility-all-items
Mar 29, 2026
Merged

diegosouzapw merged 1 commit into
diegosouzapw:mainfrom
rdself:coder/sidebar-visibility-all-items

Conversation

@rdself

@rdself rdself commented Mar 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add Appearance controls for every existing sidebar navigation item
  • auto-hide any sidebar section once all of its entries are hidden
  • centralize sidebar section metadata and sync docs, i18n, and settings persistence

User Impact

  • users can hide any sidebar entry to reduce visual clutter without disabling the underlying feature
  • empty sections collapse automatically across the full sidebar
  • direct routes and actual functionality remain unchanged

Validation

  • npm run typecheck:core
  • npx eslint 'src/shared/constants/sidebarVisibility.ts' 'src/shared/components/Sidebar.tsx' 'src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx' 'src/domain/types.ts'
  • npx prettier --check 'src/shared/constants/sidebarVisibility.ts' 'src/shared/components/Sidebar.tsx' 'src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx' 'src/i18n/messages/en.json' 'src/i18n/messages/zh-CN.json' 'src/domain/types.ts' 'docs/USER_GUIDE.md'
  • npm run test:unit (1061 passed, 0 failed)

Copilot AI review requested due to automatic review settings March 29, 2026 05:56
@kilo-code-bot

kilo-code-bot Bot commented Mar 29, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

The PR implements full sidebar visibility controls with:

  • Centralized sidebar section metadata in sidebarVisibility.ts
  • New UI in AppearanceTab for toggling any sidebar item
  • Dynamic section collapsing when all items are hidden
  • Proper validation schemas and type definitions
  • Event-driven cross-component sync

The implementation is clean, type-safe, and follows existing patterns in the codebase.

Files Reviewed (7 files)
  • docs/USER_GUIDE.md - Documentation update
  • src/shared/constants/sidebarVisibility.ts - New centralized constants
  • src/shared/components/Sidebar.tsx - Refactored to use centralized definitions
  • src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx - New UI controls
  • src/shared/validation/schemas.ts - Added validation
  • src/shared/validation/settingsSchemas.ts - Added validation
  • src/types/settings.ts - Added type definitions

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a feature allowing users to customize sidebar visibility by hiding specific navigation items. Key changes include a new UI section in the Appearance settings tab, real-time sidebar updates using custom window events, and updated validation schemas and internationalization files. The review feedback highlights several improvement opportunities: addressing a potential stale closure in a useEffect hook within the AppearanceTab component, memoizing helper functions and computed values using useCallback and useMemo to prevent unnecessary re-renders, and optimizing the sidebar item normalization logic for better performance.

@@ -25,6 +43,7 @@ export default function AppearanceTab() {
});
return unsubscribe;
}, []); // eslint-disable-line react-hooks/exhaustive-deps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The useEffect hook at line 38 has an empty dependency array, which can lead to a stale closure over the customThemeColor state variable. This can cause incorrect behavior when the theme color is updated from another source. To fix this, you should add customThemeColor to the dependency array to ensure the subscription callback always has the latest value.

Suggested change
}, []); // eslint-disable-line react-hooks/exhaustive-deps
}, [customThemeColor]);

Comment on lines +33 to +36
const getSettingsLabel = (key: string, fallback: string) =>
typeof t.has === "function" && t.has(key) ? t(key) : fallback;
const getSidebarLabel = (key: string, fallback: string) =>
typeof tSidebar.has === "function" && tSidebar.has(key) ? tSidebar(key) : fallback;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getSettingsLabel and getSidebarLabel helper functions are re-created on every render. This can lead to unnecessary re-renders of child components if these functions are passed as props. It's a good practice to memoize them using useCallback to improve performance.

Remember to import useCallback from react.

  const getSettingsLabel = useCallback((key: string, fallback: string) =>
    typeof t.has === "function" && t.has(key) ? t(key) : fallback,
  [t]);
  const getSidebarLabel = useCallback((key: string, fallback: string) =>
    typeof tSidebar.has === "function" && tSidebar.has(key) ? tSidebar(key) : fallback,
  [tSidebar]);

Comment on lines +110 to +124
const getSidebarLabel = (key: string, fallback: string) =>
typeof t.has === "function" && t.has(key) ? t(key) : fallback;

const hiddenSidebarSet = new Set(hiddenSidebarItems);
const visibleSections = SIDEBAR_SECTIONS.filter(
(section) => section.visibility !== "debug" || showDebug
)
.map((section) => ({
...section,
title: getSidebarLabel(section.titleKey, section.titleFallback),
items: section.items
.map((item) => ({ ...item, label: t(item.i18nKey) }))
.filter((item) => !hiddenSidebarSet.has(item.id)),
}))
.filter((section) => section.items.length > 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getSidebarLabel function is recreated on every render, and visibleSections is recalculated on every render. This can impact performance. It's recommended to memoize both using useCallback and useMemo respectively. This will prevent re-computation unless their dependencies change.

Remember to import useCallback and useMemo from react.

  const getSidebarLabel = useCallback((key: string, fallback: string) =>
    typeof t.has === "function" && t.has(key) ? t(key) : fallback,
  [t]);

  const visibleSections = useMemo(() => {
    const hiddenSidebarSet = new Set(hiddenSidebarItems);
    return SIDEBAR_SECTIONS.filter(
      (section) => section.visibility !== "debug" || showDebug
    )
      .map((section) => ({
        ...section,
        title: getSidebarLabel(section.titleKey, section.titleFallback),
        items: section.items
          .map((item) => ({ ...item, label: t(item.i18nKey) }))
          .filter((item) => !hiddenSidebarSet.has(item.id)),
      }))
      .filter((section) => section.items.length > 0);
  }, [hiddenSidebarItems, showDebug, getSidebarLabel, t]);

Comment on lines +129 to +144
export function normalizeHiddenSidebarItems(value: unknown): HideableSidebarItemId[] {
if (!Array.isArray(value)) return [];

const hiddenItems = new Set<HideableSidebarItemId>();

for (const item of value) {
if (
typeof item === "string" &&
HIDEABLE_SIDEBAR_ITEM_IDS.includes(item as HideableSidebarItemId)
) {
hiddenItems.add(item as HideableSidebarItemId);
}
}

return HIDEABLE_SIDEBAR_ITEM_IDS.filter((item) => hiddenItems.has(item));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of normalizeHiddenSidebarItems uses Array.prototype.includes inside a loop, which has a time complexity of O(N*M). For better performance, especially if the list of hidden items grows, you can convert HIDEABLE_SIDEBAR_ITEM_IDS to a Set for O(1) lookups.

export function normalizeHiddenSidebarItems(value: unknown): HideableSidebarItemId[] {
  if (!Array.isArray(value)) return [];

  const validIds = new Set(HIDEABLE_SIDEBAR_ITEM_IDS);
  const hiddenItems = new Set<HideableSidebarItemId>();

  for (const item of value) {
    if (typeof item === "string" && validIds.has(item as HideableSidebarItemId)) {
      hiddenItems.add(item as HideableSidebarItemId);
    }
  }

  return HIDEABLE_SIDEBAR_ITEM_IDS.filter((item) => hiddenItems.has(item));
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds full per-item sidebar visibility controls across all sidebar sections, centralizing sidebar metadata and persisting hidden items in settings. This expands the previous partial implementation to cover every existing sidebar entry and auto-hides empty sections.

Changes:

  • Introduces a centralized sidebar definition/visibility module (SIDEBAR_SECTIONS, hideable IDs, normalization helper).
  • Updates Sidebar rendering to filter items/sections based on persisted hiddenSidebarItems and a settings-updated event.
  • Adds Appearance UI to toggle visibility for every sidebar entry; updates settings types, validation, defaults, i18n, and docs.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/types/settings.ts Adds hiddenSidebarItems / hideHealthCheckLogs to settings typing.
src/shared/validation/settingsSchemas.ts Validates hiddenSidebarItems in the /api/settings PATCH schema.
src/shared/validation/schemas.ts Mirrors hiddenSidebarItems validation in the shared schema barrel.
src/shared/constants/sidebarVisibility.ts Centralizes sidebar section/item metadata and normalization logic.
src/shared/components/Sidebar.tsx Renders sections from centralized metadata; filters hidden items and auto-hides empty sections.
src/lib/db/settings.ts Adds default hiddenSidebarItems: [] in settings read defaults.
src/i18n/messages/en.json Adds sidebar section labels and settings strings for visibility UI.
src/i18n/messages/zh-CN.json Same as en.json for zh-CN.
src/domain/types.ts Documents hiddenSidebarItems in AppSettings JSDoc.
src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx Adds UI to toggle hidden sidebar items and broadcasts updates to Sidebar.
docs/USER_GUIDE.md Updates settings tab count/table to reflect “General” and sidebar visibility controls.
Comments suppressed due to low confidence (1)

src/shared/validation/settingsSchemas.ts:35

  • fallbackStrategy in this settings schema does not include the supported value "strict-random" (it is present in the broader schema and strategy constants). Since /api/settings uses this schema for PATCH validation, attempts to set strict-random will be rejected. Add "strict-random" here (or reference the shared SETTINGS_FALLBACK_STRATEGY_VALUES) to keep validation consistent.
  hideHealthCheckLogs: z.boolean().optional(),
  hiddenSidebarItems: z.array(z.enum(HIDEABLE_SIDEBAR_ITEM_IDS)).optional(),
  // Routing settings (#134)
  fallbackStrategy: z
    .enum(["fill-first", "round-robin", "p2c", "random", "least-used", "cost-optimized"])
    .optional(),
  wildcardAliases: z.array(z.object({ pattern: z.string(), target: z.string() })).optional(),
  stickyRoundRobinLimit: z.number().int().min(0).max(1000).optional(),

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{collapsed && showTitle && (
<div className="border-t border-black/5 dark:border-white/5 mb-2" />
)}
{section.items.map(renderNavLink)}

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new section wrapper <div key={section.id}> makes the sidebar links no longer direct children of the <nav> that has space-y-1, so vertical spacing between individual links will disappear within each section. Add spacing on the wrapper or an inner container (e.g., apply space-y-1 to the section’s items container) to preserve the original layout.

Suggested change
{section.items.map(renderNavLink)}
<div className="space-y-1">
{section.items.map(renderNavLink)}
</div>

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +45
const [hiddenSidebarItems, setHiddenSidebarItems] = useState<string[]>([]);

// Check if debug mode is enabled
useEffect(() => {
const applySettings = (data) => {
setShowDebug(data?.enableRequestLogs === true);
setHiddenSidebarItems(normalizeHiddenSidebarItems(data?.[HIDDEN_SIDEBAR_ITEMS_SETTING_KEY]));
};

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hiddenSidebarItems state is typed as string[], but it only ever contains normalized HideableSidebarItemId values. Consider typing this state as HideableSidebarItemId[] to keep the Set/filter logic type-safe and prevent accidentally storing arbitrary strings later.

Copilot uses AI. Check for mistakes.
Comment thread src/i18n/messages/en.json
Comment on lines +1732 to +1734
"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",

Copilot AI Mar 29, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New i18n keys were added here (and under sidebar) but not to the other locale bundles in src/i18n/messages/*.json (e.g., ar/pt-BR have no primarySection/sidebarVisibility* keys). If the repo expects per-locale message files to stay key-synchronized, please add these keys to all locales (even if using English placeholders) to avoid missing-string fallbacks and future runtime errors where t() is used without has checks.

Suggested change
"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",

Copilot uses AI. Check for mistakes.
@diegosouzapw
diegosouzapw merged commit fcc48cc into diegosouzapw:main Mar 29, 2026
1 check passed
@diegosouzapw

Copy link
Copy Markdown
Owner

Thanks @rdself for this great contribution! 🎉 The feature is now merged and will be part of the next release. We appreciate your effort!

@rdself
rdself deleted the coder/sidebar-visibility-all-items branch April 16, 2026 01:14
@diegosouzapw diegosouzapw mentioned this pull request Apr 22, 2026
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants