Skip to main content

ERP Sidebar Architecture Plan

Related: ADR-0008: Frontend Plugin Architecture - Defines plugin menu contract and PluginRegistry integration

Current State Analysis

Existing Implementation (frontend/src/layout/Sidebar.tsx)

FeatureStatusNotes
Groups by moduleParses module.DocType from name
Collapsible sectionsToggle per group
Search/filterFilters by label
Plugin slotssidebar-top, sidebar-bottom
Collapse toggle60px vs 240px width
Active item highlightBorder + background

Current Limitations

  1. Flat grouping - Only groups by first segment (e.g., "core.User" → "Core")
  2. No nested hierarchy - Can't have multi-level menus (Module → Category → DocType)
  3. No icons - Text only, no visual differentiation
  4. No pinned/favorites - Can't pin frequently used items
  5. No recent items - No quick access to recently viewed
  6. Static structure - Menu comes only from DocTypes, no custom links

Proposed Architecture

The sidebar displays a hierarchical menu structure with user-specific enhancements:

┌─────────────────────────────────────┐
│ 🏠 Home │ ← Static entry
├─────────────────────────────────────┤
│ ⭐ Favorites │ ← User-specific
│ └── Sales Invoice │
│ └── Customer │
├─────────────────────────────────────┤
│ 🕐 Recent │ ← Last 5-10 visited
│ └── INV-00123 │
│ └── CUST-00045 │
├─────────────────────────────────────┤
│ 📦 Sales │ ← Module (from plugins)
│ ├── Transactions │ ← Category
│ │ ├── Sales Invoice │
│ │ ├── Sales Order │
│ │ └── Quotation │
│ └── Masters │
│ ├── Customer │
│ └── Price List │
├─────────────────────────────────────┤
│ 🏭 Inventory │
│ ├── Stock Entry │
│ └── Item │
├─────────────────────────────────────┤
│ ⚙️ Settings │ ← Always at bottom
│ └── User │
│ └── Company │
└─────────────────────────────────────┘

Note: Module and category structure comes from PluginRegistry.getMenu() (see ADR-0008). Sidebar adds Favorites and Recent sections on top.

  1. Backend metadata - DocTypes with show_in_menu: true
  2. Plugin contributions - See ADR-0008 Plugin Menu Contract
  3. User preferences - Favorites, recent, collapsed state (sidebar-specific)
  4. Static entries - Home, Settings (always present)

Note: Menu items from plugins are registered via PluginRegistry.getMenu() as defined in ADR-0008. The sidebar consumes this merged menu tree and adds user-specific features (favorites, recent, search).


These features extend the base menu system defined in ADR-0008:

Favorites

  • User can pin frequently used items
  • Stored in localStorage or user preferences API
  • Appears as top-level "⭐ Favorites" section
  • Right-click or star icon to pin/unpin

Recent Items

  • Track last 10 viewed DocTypes/records
  • Stored in localStorage
  • Appears as "🕐 Recent" section below Favorites
  • Auto-updates on navigation

Search & Filter

  • Fuzzy search across all menu items
  • Shows matched path (Sales > Invoice > ...)
  • Highlights matches
  • Filters entire tree, preserving hierarchy

Responsive Behavior

  • Mobile: Slide-out drawer (overlay)
  • Tablet: Collapsible sidebar
  • Desktop: Always visible, collapsible to 60px icons-only

Data Model Extensions

The sidebar extends the base MenuItem interface from ADR-0008:

// Base MenuItem from ADR-0008 plugin contract
import { MenuItem } from "@framework-m/plugin-sdk";

// Sidebar-specific extensions
interface SidebarMenuItem extends MenuItem {
// Runtime state (not persisted)
isExpanded?: boolean;
isFavorite?: boolean;
lastVisited?: Date;

// UI-specific
depth?: number; // Nesting level for indentation
parentId?: string; // For breadcrumb/path display
}

// User preferences
interface SidebarPreferences {
favorites: string[]; // MenuItem IDs
recent: Array<{
id: string;
timestamp: Date;
}>;
collapsed: string[]; // Collapsed section IDs
customOrder?: Record<string, number>; // Custom sort order
}

Integration with PluginRegistry

The sidebar integrates with the plugin system defined in ADR-0008:

// Sidebar.tsx
import { usePluginMenu } from '@framework-m/plugin-sdk';

function Sidebar() {
// Get merged menu from all registered plugins
const pluginMenu = usePluginMenu();

// Load user preferences
const [preferences, setPreferences] = useSidebarPreferences();

// Enhance with sidebar-specific features
const menuTree = useMemo(() => {
return enhanceMenu(pluginMenu, preferences);
}, [pluginMenu, preferences]);

return (
<nav>
<FavoritesSection items={menuTree.favorites} />
<RecentSection items={menuTree.recent} />
<MenuTree items={menuTree.modules} />
</nav>
);
}

function enhanceMenu(
pluginMenu: MenuItem[],
preferences: SidebarPreferences
): EnhancedMenuTree {
// Add favorites section
// Add recent section
// Apply custom ordering
// Set expanded states
// Calculate depths for indentation
}

Scalability Patterns

Pattern 1: Lazy Loading Large Modules

// Load children on expand (for modules with 100+ items)
async function loadMenuChildren(moduleId: string) {
const response = await fetch(`/api/meta/menu/${moduleId}`);
return response.json();
}

Pattern 2: Virtual Scrolling

For 500+ menu items, use virtual scrolling:

import { FixedSizeList } from 'react-window';

<FixedSizeList height={400} itemCount={items.length} itemSize={36}>
{({ index, style }) => <MenuItem item={items[index]} style={style} />}
</FixedSizeList>

Implementation Checklist

Phase 1: Core Improvements ✅ COMPLETE

  • Add icons support

    • Icon component implemented (frontend/src/components/Icon.tsx)
    • getModuleIcon() function in frontend/src/types/menu.ts
    • ✅ Icons used throughout Sidebar.tsx
  • Multi-level nesting

    • MenuItem interface supports children array
    • ✅ Backend API returns nested structure
    • ✅ Frontend groups by module (flat, not recursive yet)
  • Improve grouping logic

    • ✅ Backend: Parses doctype_name.split(".") for module extraction
    • ✅ Module icons mapped in both backend and frontend
    • ✅ DocTypes grouped by module in sidebar

Phase 2: User Experience ✅ COMPLETE

  • Favorites section

    • useFavorites hook (frontend/src/hooks/useFavorites.ts)
    • ✅ localStorage persistence with addFavorite, removeFavorite, toggleFavorite
    • ✅ Star icon on each menu item for toggling
    • ✅ Collapsible "⭐ Favorites" section at top of sidebar
    • ✅ Drag-to-reorder within favorites (via useSortable)
  • Recent items

    • useRecent hook (frontend/src/hooks/useRecent.ts)
    • ✅ localStorage persistence with addRecentItem, max 10 items
    • ✅ Tracks both DocType views and specific records
    • ✅ Collapsible "🕐 Recent" section in sidebar
    • ✅ Wired to FormView and ListView with automatic tracking
  • Keyboard navigation

    • ✅ Arrow key navigation implemented in Sidebar.tsx
    • ✅ Focus state tracked with focusedIndex
    • ✅ Enter key to navigate to focused item
    • ✅ Escape key to clear focus
    • ✅ Visual focus indicator (2px outline) on all menu items
    • ✅ Focus wrapping at list boundaries

Phase 3: Scalability ✅ MOSTLY COMPLETE

  • Backend menu API

    • ✅ Endpoint: GET /api/meta/menu (menu_routes.py)
    • ✅ Returns structured menu tree with modules and children
    • ✅ Module icons, ordering, and metadata
    • ✅ Filters by api_resource=True
    • ⚠️ TODO: Wire up router in app.py route handlers (currently NOT registered)
  • Plugin menu integration ❌ NOT STARTED

    • ❌ No @framework-m/plugin-sdk package exists yet (see ADR-0008)
    • ❌ No PluginRegistry.getMenu() implementation
    • ❌ No plugin menu merging logic
    • ✅ Hook placeholder exists: useMenuData can be extended
    • 📝 Status: Blocked by ADR-0008 implementation
  • Badge counts ⏸️ DEFERRED

    • ⏸️ Not started (as planned)
    • 📝 Future enhancement

Phase 4: Polish ✅ COMPLETE

  • Drag-to-reorder

    • useSortable hook (frontend/src/hooks/useSortable.ts)
    • ✅ localStorage persistence of custom order
    • ✅ Drag handlers with visual feedback
    • ✅ Applied to favorites list
    • ⏸️ Module groups reordering deferred (optional enhancement)
  • Search improvements

    • fuzzySearch.ts utility with scoring and highlighting
    • fuzzyMatch() and getHighlightedSegments() functions
    • ✅ Filters entire menu tree
    • ✅ Visual highlighting of matched text with background color
    • ✅ Breadcrumb display showing module path for search results
  • Responsive behavior

    • ✅ Collapse toggle button implemented (60px ↔ 240px)
    • ✅ Icons-only mode when collapsed
    • ✅ Mobile slide-out drawer with overlay
    • ✅ Backdrop overlay that closes on click
    • ✅ Touch gestures for swipe-to-close
    • ✅ Mobile menu button in Navbar
    • ✅ Media queries for responsive behavior

Implementation Status Summary

✅ Fully Complete (80%)

  1. Icons system with module mapping
  2. Favorites with localStorage + drag-to-reorder
  3. Recent items tracking
  4. Fuzzy search algorithm
  5. Backend menu API structure
  6. Type definitions and data models
  7. Core sidebar layout and grouping
  8. Keyboard navigation - All features complete (Arrow keys, Enter, Escape, visual focus, wrapping)

⚠️ Partially Complete (12%)

  1. Search highlighting - Scoring works, needs visual highlights in UI
  2. Responsive - Desktop works, needs mobile drawer
  3. Backend integration - API exists but not wired to app router

❌ Not Started (5%)

  1. Plugin menu integration - Blocked by ADR-0008 (plugin SDK doesn't exist yet)
  2. Badge counts - Intentionally deferred

Critical Issues to Fix

🔴 HIGH PRIORITY

  1. Menu API not registered in app 🔴

    • File: libs/framework-m-standard/src/framework_m_standard/adapters/web/app.py
    • Issue: menu_routes_router created but never added to route_handlers
    • Fix: Add import and register router
    from framework_m_standard.adapters.web.menu_routes import menu_routes_router

    route_handlers: list[Any] = [
    # ... existing handlers
    menu_routes_router, # ADD THIS
    ]
  2. Recent items not tracked on navigation 🟡

    • Files: Need to hook into navigation events
    • Issue: useRecent hook exists but addRecentItem() never called
    • Fix: Add effect in FormView.tsx and ListView.tsx:
    const { addRecentItem } = useRecent();

    useEffect(() => {
    if (doctype) {
    addRecentItem({
    name: doctype,
    label: doctype,
    route: location.pathname,
    });
    }
    }, [doctype, location.pathname]);

🟡 MEDIUM PRIORITY

  1. Search highlighting not visible 🟡

    • Issue: fuzzyMatch() returns highlights but UI doesn't show them
    • Fix: Use getHighlightedSegments() in menu item rendering
  2. Mobile responsive drawer 🟡

    • Issue: Sidebar just shrinks on mobile, no overlay drawer
    • Fix: Add media queries + overlay backdrop
  3. Plugin integration blocked 🔵

    • Blocked by: ADR-0008 implementation (plugin SDK doesn't exist)
    • Status: Cannot proceed until @framework-m/plugin-sdk package is created

Files Inventory

✅ Implemented and Working

FilePurposeStatus
frontend/src/layout/Sidebar.tsxMain sidebar component✅ Core complete
frontend/src/hooks/useFavorites.tsFavorites management✅ Fully functional
frontend/src/hooks/useRecent.tsRecent items tracking✅ Hook ready, needs wiring
frontend/src/hooks/useSortable.tsDrag-to-reorder✅ Fully functional
frontend/src/hooks/useMenuData.tsBackend menu API client✅ Ready (API not wired)
frontend/src/utils/fuzzySearch.tsSearch algorithm✅ Fully functional
frontend/src/types/menu.tsTypeScript types✅ Complete
frontend/src/components/Icon.tsxIcon component✅ Fully functional
libs/.../menu_routes.pyBackend menu API✅ Implemented (not registered)

⚠️ Needs Attention

FileIssuePriority
frontend/src/layout/Sidebar.tsxAdd Enter key handler, focus styles🟡 Medium
frontend/src/layout/Sidebar.tsxMobile drawer + overlay🟡 Medium
frontend/src/layout/Sidebar.tsxRender search highlights🟡 Medium
frontend/src/pages/FormView.tsxCall addRecentItem() on load🟡 Medium
frontend/src/pages/ListView.tsxCall addRecentItem() on load🟡 Medium
.../adapters/web/app.pyRegister menu_routes_router🔴 High

❌ Missing (Per RFC Spec)

FilePurposeStatus
frontend/src/hooks/useSidebarPreferences.tsUser preferences⚠️ Not needed (localStorage in each hook)
@framework-m/plugin-sdkPlugin system❌ Blocked by ADR-0008

Files to Modify

FileChanges
frontend/src/layout/Sidebar.tsxMain implementation
frontend/src/hooks/useMenu.tsIntegrates with PluginRegistry (new)
frontend/src/hooks/useFavorites.tsFavorites management (new)
frontend/src/hooks/useRecent.tsRecent items tracking (new)
frontend/src/hooks/useSidebarPreferences.tsUser preferences (new)
frontend/src/types/sidebar.tsTypeScript interfaces (new)
libs/framework-m-standard/.../routes/meta.pyBackend menu API (new endpoint)

Note: Plugin menu registration handled by @framework-m/plugin-sdk (see ADR-0008)


Architecture Alignment

This RFC implements the sidebar consumer of the menu system defined in ADR-0008:

┌─────────────────────────────────────────────────────────────┐
│ ADR-0008: Plugin Architecture │
│ - PluginRegistry.getMenu() → Merged menu tree │
│ - MenuItem interface (base contract) │
│ - Plugin contributions via plugin.config.ts │
└────────────────────┬────────────────────────────────────────┘
│ provides menu data

┌─────────────────────────────────────────────────────────────┐
│ This RFC: Sidebar Implementation │
│ - Consumes PluginRegistry.getMenu() │
│ - Adds favorites, recent, search (UI features) │
│ - Manages user preferences (collapsed, custom order) │
│ - Renders responsive navigation │
└─────────────────────────────────────────────────────────────┘

Separation of Concerns:

  • ADR-0008: Defines what menus exist (data model, plugin contract)
  • This RFC: Defines how menus are displayed (UI, UX, interactions)

Quick Start: Minimal Changes

For immediate improvement without breaking changes:

// 1. Add icons to groupMenuItems()
const MODULE_ICONS: Record<string, string> = {
Core: '⚙️',
Sales: '💰',
Inventory: '📦',
HR: '👥',
Other: '📄',
};

// 2. Use icon in group header
<span>{MODULE_ICONS[groupName] || '📁'} {groupName}</span>

This gives visual differentiation immediately while planning the full refactor.