# DeesAppui

A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management. 🚀

## Quick Start

```typescript
import { html, DeesElement, customElement } from '@design.estate/dees-element';
import { DeesAppui } from '@design.estate/dees-catalog';

@customElement('my-app')
class MyApp extends DeesElement {
  private appui: DeesAppui;

  async firstUpdated() {
    this.appui = this.shadowRoot.querySelector('dees-appui');

    // Configure with views and menu
    this.appui.configure({
      branding: { logoIcon: 'lucide:box', logoText: 'My App' },
      views: [
        { id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
        { id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
      ],
      mainMenu: {
        sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
      },
      defaultView: 'dashboard'
    });
  }

  render() {
    return html`<dees-appui></dees-appui>`;
  }
}
```

## Architecture Overview

The DeesAppui shell consists of several interconnected components:

```
┌─────────────────────────────────────────────────────────────────────┐
│  AppBar (dees-appui-appbar)                                         │
│  ├── Menus (File, Edit, View...)                                    │
│  ├── Breadcrumbs                                                    │
│  ├── User Profile + Dropdown                                        │
│  └── Activity Log Toggle                                            │
├─────────────┬───────────────────────────────────┬───────────────────┤
│ Main Menu   │  Content Area                     │  Activity Log     │
│ (collapsed/ │  ├── Content Tabs                 │  (slide panel)    │
│  expanded)  │  │   (closable, from tables/lists)│                   │
│             │  └── View Container               │                   │
│ ┌─────────┐ │      └── Active View              │                   │
│ │ 🏠 Home │ ├─────────────────────────────────┐ │                   │
│ │ 📁 Files│ │ Secondary Menu                  │ │                   │
│ │ ⚙ Settings ├── Collapsible Groups            │ │                   │
│ │         │ │   ├── Item 1                    │ │                   │
│ └─────────┘ │   ├── Item 2 (with badge)       │ │                   │
│             │   └── Item 3                    │ │                   │
└─────────────┴─────────────────────────────────┴───────────────────────┘
```

---

## Configuration API

### `configure(config: IAppConfig)`

Configure the entire application shell with a single configuration object.

```typescript
interface IAppConfig {
  branding?: IBrandingConfig;
  appBar?: IAppBarConfig;
  views: IViewDefinition[];
  mainMenu?: IMainMenuConfig;
  defaultView?: string;
  activityLog?: IActivityLogConfig;
  onViewChange?: (viewId: string, view: IViewDefinition) => void;
  onSearch?: (query: string) => void;
}
```

### View Definition

```typescript
interface IViewDefinition {
  id: string;                           // Unique identifier
  name: string;                         // Display name
  iconName?: string;                    // Icon (e.g., 'lucide:home')
  content:                              // View content
    | string                            // Tag name ('my-component')
    | (new () => HTMLElement)           // Class constructor
    | (() => TemplateResult)            // Template function
    | (() => Promise<...>);             // Async for lazy loading
  secondaryMenu?: ISecondaryMenuGroup[];
  contentTabs?: ITab[];
  route?: string;                       // URL route (default: id)
  badge?: string | number;
  cache?: boolean;                      // Cache view instance (default: true)
}
```

---

## Programmatic APIs

### App Bar API

Control the top application bar.

```typescript
// Set menu items (File, Edit, View, etc.)
appui.setAppBarMenus([
  {
    name: 'File',
    submenu: [
      { name: 'New', shortcut: 'Cmd+N', action: () => {} },
      { name: 'Save', shortcut: 'Cmd+S', action: () => {} },
    ]
  }
]);

// Update single menu
appui.updateAppBarMenu('File', { submenu: [...newItems] });

// Breadcrumbs
appui.setBreadcrumbs('Dashboard > Settings > Profile');
appui.setBreadcrumbs(['Dashboard', 'Settings', 'Profile']);

// User profile
appui.setUser({
  name: 'John Doe',
  email: 'john@example.com',
  avatar: '/avatars/john.png',
  status: 'online'  // 'online' | 'offline' | 'busy' | 'away'
});

appui.setProfileMenuItems([
  { name: 'Profile', iconName: 'lucide:user', action: () => {} },
  { divider: true },
  { name: 'Sign Out', iconName: 'lucide:log-out', action: () => {} }
]);

// Search
appui.setSearchVisible(true);
appui.onSearch((query) => console.log('Search:', query));

// Window controls (for Electron/Tauri apps)
appui.setWindowControlsVisible(false);
```

### Main Menu API (Left Sidebar)

Control the main navigation menu.

```typescript
// Set entire menu
appui.setMainMenu({
  logoIcon: 'lucide:box',
  logoText: 'My App',
  groups: [
    {
      name: 'Main',
      tabs: [
        { key: 'dashboard', iconName: 'lucide:home', action: () => {} },
        { key: 'inbox', iconName: 'lucide:inbox', badge: 5, action: () => {} },
      ]
    }
  ],
  bottomTabs: [
    { key: 'settings', iconName: 'lucide:settings', action: () => {} }
  ]
});

// Update specific group
appui.updateMainMenuGroup('Main', { tabs: [...newTabs] });

// Add/remove items
appui.addMainMenuItem('Main', { key: 'tasks', iconName: 'lucide:check', action: () => {} });
appui.removeMainMenuItem('Main', 'tasks');

// Selection
appui.setMainMenuSelection('dashboard');

// Visibility control
appui.setMainMenuCollapsed(true);     // Collapse to icon-only sidebar
appui.setMainMenuVisible(false);       // Hide completely

// Badges
appui.setMainMenuBadge('inbox', 12);
appui.clearMainMenuBadge('inbox');
```

---

## Secondary Menu API 📋

The secondary menu is a contextual sidebar that appears next to the main content area. It supports **collapsible groups** with icons and badges, making it perfect for:

- **Settings pages** (grouped settings categories)
- **File browsers** (folder trees)
- **Project navigation** (grouped by category)
- **Documentation** (chapters/sections)

### Collapsible Groups

Groups can be collapsed/expanded by clicking the group header. The state is visually indicated with an icon rotation.

```typescript
// Set secondary menu with collapsible groups
appui.setSecondaryMenu({
  heading: 'Settings',
  groups: [
    {
      name: 'Account',
      iconName: 'lucide:user',        // Group icon
      collapsed: false,                // Initial state (default: false)
      items: [
        { key: 'profile', iconName: 'lucide:user', action: () => showProfile() },
        { key: 'security', iconName: 'lucide:shield', badge: '!', badgeVariant: 'warning', action: () => showSecurity() },
        { key: 'billing', iconName: 'lucide:credit-card', action: () => showBilling() }
      ]
    },
    {
      name: 'Preferences',
      iconName: 'lucide:settings',
      collapsed: true,                 // Start collapsed
      items: [
        { key: 'notifications', iconName: 'lucide:bell', action: () => {} },
        { key: 'appearance', iconName: 'lucide:palette', action: () => {} },
        { key: 'language', iconName: 'lucide:globe', action: () => {} }
      ]
    }
  ]
});
```

### Secondary Menu Item Properties

```typescript
interface ISecondaryMenuItem {
  key: string;                    // Unique identifier
  iconName?: string;              // Icon (e.g., 'lucide:user')
  action: () => void;             // Click handler
  badge?: string | number;        // Badge text/count
  badgeVariant?: 'default' | 'success' | 'warning' | 'error';
}

interface ISecondaryMenuGroup {
  name: string;                   // Group name (shown in header)
  iconName?: string;              // Group icon
  collapsed?: boolean;            // Initial collapsed state
  items: ISecondaryMenuItem[];    // Items in this group
}
```

### Updating Secondary Menu

```typescript
// Update a specific group
appui.updateSecondaryMenuGroup('Account', {
  items: [...newItems]
});

// Add item to a group
appui.addSecondaryMenuItem('Account', {
  key: 'api-keys',
  iconName: 'lucide:key',
  action: () => showApiKeys()
});

// Selection (highlights the item)
appui.setSecondaryMenuSelection('profile');

// Visibility control
appui.setSecondaryMenuCollapsed(true);   // Collapse panel
appui.setSecondaryMenuVisible(false);     // Hide completely

// Clear
appui.clearSecondaryMenu();
```

### View-Specific Secondary Menus

Each view can define its own secondary menu that appears when the view is activated:

```typescript
// In view definition
{
  id: 'settings',
  name: 'Settings',
  content: 'my-settings-view',
  secondaryMenu: [
    {
      name: 'General',
      items: [
        { key: 'account', iconName: 'lucide:user', action: () => {} },
        { key: 'security', iconName: 'lucide:shield', action: () => {} }
      ]
    }
  ]
}

// Or set dynamically in view's onActivate hook
onActivate(context: IViewActivationContext) {
  context.appui.setSecondaryMenu({
    heading: 'Project Files',
    groups: [...]
  });
}
```

---

## Content Tabs API 📑

Content tabs appear above the main view content. They're designed for **opening multiple items** from tables, lists, or other data sources—similar to browser tabs or IDE editor tabs.

### Common Use Cases

- **Table row details** - Click a row to open it as a tab
- **Document editing** - Open multiple documents
- **Entity inspection** - View customer, order, product details
- **Multi-file editing** - Edit multiple configuration files

### Closable Tabs

Tabs can be closable, allowing users to open items, work with them, and close when done:

```typescript
// Set initial tabs
appui.setContentTabs([
  { key: 'overview', iconName: 'lucide:home', action: () => showOverview() },
  { key: 'activity', iconName: 'lucide:activity', action: () => showActivity() }
]);

// Add a closable tab when user clicks a table row
table.addEventListener('row-click', (e) => {
  const item = e.detail.item;

  appui.addContentTab({
    key: `item-${item.id}`,
    label: item.name,                      // Display label
    iconName: 'lucide:file',
    closable: true,                        // Allow closing
    action: () => showItemDetails(item)
  });

  // Select the new tab
  appui.selectContentTab(`item-${item.id}`);
});

// Handle tab close
appui.addEventListener('tab-close', (e) => {
  const tabKey = e.detail.key;
  // Cleanup resources if needed
  console.log(`Tab ${tabKey} closed`);
});
```

### Tab Management

```typescript
// Add/remove tabs
appui.addContentTab({
  key: 'debug',
  iconName: 'lucide:bug',
  closable: true,
  action: () => {}
});
appui.removeContentTab('debug');

// Select tab
appui.selectContentTab('preview');

// Get current tab
const current = appui.getSelectedContentTab();

// Visibility control
appui.setContentTabsVisible(false);        // Hide tab bar

// Auto-hide when only one tab
appui.setContentTabsAutoHide(true, 1);     // Hide when ≤ 1 tab
```

### Opening Items from Tables/Lists

A common pattern is opening table rows as closable tabs:

```typescript
@customElement('my-customers-view')
class MyCustomersView extends DeesElement {
  private appui: DeesAppui;

  onActivate(context: IViewActivationContext) {
    this.appui = context.appui;

    // Set base tabs
    this.appui.setContentTabs([
      { key: 'list', label: 'All Customers', iconName: 'lucide:users', action: () => this.showList() }
    ]);
  }

  render() {
    return html`
      <dees-table
        .data=${this.customers}
        @row-dblclick=${this.openCustomerTab}
      ></dees-table>
    `;
  }

  openCustomerTab(e: CustomEvent) {
    const customer = e.detail.item;
    const tabKey = `customer-${customer.id}`;

    // Check if tab already exists
    const existingTab = this.appui.getSelectedContentTab();
    if (existingTab?.key === tabKey) {
      return; // Already viewing this customer
    }

    // Add new closable tab
    this.appui.addContentTab({
      key: tabKey,
      label: customer.name,
      iconName: 'lucide:user',
      closable: true,
      action: () => this.showCustomerDetails(customer)
    });

    this.appui.selectContentTab(tabKey);
  }

  showCustomerDetails(customer: Customer) {
    // Render customer details
    this.currentView = html`<customer-details .customer=${customer}></customer-details>`;
  }

  showList() {
    this.currentView = html`<dees-table ...></dees-table>`;
  }
}
```

---

## Activity Log API 📊

The activity log is a slide-out panel on the right side showing user actions and system events.

### Activity Log Toggle

The appbar includes a toggle button with a badge showing the entry count:

```typescript
// Control visibility
appui.setActivityLogVisible(true);     // Show panel
appui.toggleActivityLog();             // Toggle state
const isVisible = appui.getActivityLogVisible();

// The toggle button automatically shows entry count
// Add entries and the badge updates automatically
```

### Adding Entries

```typescript
// Add single entry
appui.activityLog.add({
  type: 'create',       // 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom'
  user: 'John Doe',
  message: 'created a new invoice',
  iconName: 'lucide:file-plus',  // Optional custom icon
  data: { invoiceId: '123' }     // Optional metadata
});

// Add multiple entries (e.g., from backend)
appui.activityLog.addMany([...entries]);

// Clear all entries
appui.activityLog.clear();

// Query entries
const entries = appui.activityLog.getEntries();
const filtered = appui.activityLog.filter({ user: 'John', type: 'create' });
const searched = appui.activityLog.search('invoice');
```

### Activity Entry Types

Each type has a default icon that can be overridden:

| Type | Default Icon | Use Case |
|------|--------------|----------|
| `login` | `lucide:log-in` | User sign-in |
| `logout` | `lucide:log-out` | User sign-out |
| `view` | `lucide:eye` | Page/item viewed |
| `create` | `lucide:plus` | New item created |
| `update` | `lucide:pencil` | Item modified |
| `delete` | `lucide:trash` | Item deleted |
| `custom` | `lucide:activity` | Custom events |

---

## Navigation API

Navigate between views programmatically.

```typescript
// Navigate to view
await appui.navigateToView('settings');
await appui.navigateToView('settings', { section: 'profile' });

// Get current view
const current = appui.getCurrentView();

// Subscribe to view changes
appui.viewChanged$.subscribe((event) => {
  console.log(`Navigated to: ${event.viewId}`);
});

// Subscribe to lifecycle events
appui.viewLifecycle$.subscribe((event) => {
  if (event.type === 'activated') {
    console.log(`View ${event.viewId} activated`);
  }
});
```

---

## View Lifecycle Hooks

Views can implement lifecycle hooks to respond to activation/deactivation.

```typescript
import { DeesElement, customElement } from '@design.estate/dees-element';
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';

@customElement('my-settings-view')
class MySettingsView extends DeesElement implements IViewLifecycle {
  /**
   * Called when view is activated (displayed)
   * Receives typed context with appui reference
   */
  async onActivate(context: IViewActivationContext) {
    const { appui, viewId, params } = context;

    // Set view-specific secondary menu
    appui.setSecondaryMenu({
      heading: 'Settings',
      groups: [{ name: 'Options', items: [...] }]
    });

    // Set view-specific tabs
    appui.setContentTabs([...]);

    // Load data based on route params
    if (params?.section) {
      await this.loadSection(params.section);
    }
  }

  /**
   * Called when view is deactivated (hidden)
   */
  onDeactivate() {
    this.cleanup();
  }

  /**
   * Called before navigation away
   * Return false or a message string to block navigation
   */
  canDeactivate(): boolean | string {
    if (this.hasUnsavedChanges) {
      return 'You have unsaved changes. Leave anyway?';
    }
    return true;
  }
}
```

### IViewActivationContext

```typescript
interface IViewActivationContext {
  appui: DeesAppui;           // Reference to the app shell
  viewId: string;                  // The view ID being activated
  params?: Record<string, string>; // Route parameters
}
```

---

## Routing

Routes are automatically registered from view definitions using `domtools.router`.

```typescript
const views = [
  { id: 'dashboard', route: 'dashboard', ... },
  { id: 'settings', route: 'settings/:section?', ... },  // Parameterized
  { id: 'user', route: 'users/:id', ... },
];

// URL: #dashboard → navigates to dashboard view
// URL: #settings/profile → navigates to settings with params.section = 'profile'
// URL: #users/123 → navigates to user with params.id = '123'
```

### Hash-based Routing

The router uses hash-based routing by default (`#viewId`). URLs are automatically synchronized when navigating via `navigateToView()`.

---

## View Caching

Views are cached by default. When navigating away and back, the same DOM element is reused (hidden/shown) rather than destroyed and recreated.

```typescript
// Disable caching for a specific view
{
  id: 'reports',
  name: 'Reports',
  content: 'my-reports-view',
  cache: false  // Always recreate this view
}
```

---

## Lazy Loading

Use async content functions for lazy loading views.

```typescript
{
  id: 'analytics',
  name: 'Analytics',
  content: async () => {
    const module = await import('./views/analytics.js');
    return module.AnalyticsView;
  }
}
```

---

## RxJS Observables

The component exposes RxJS Subjects for reactive programming.

```typescript
// View lifecycle events
appui.viewLifecycle$.subscribe((event) => {
  // event.type: 'loading' | 'activated' | 'deactivated' | 'loaded' | 'loadError'
  // event.viewId: string
  // event.element?: HTMLElement
  // event.params?: Record<string, string>
  // event.error?: unknown
});

// View change events
appui.viewChanged$.subscribe((event) => {
  // event.viewId: string
  // event.view: IViewDefinition
  // event.previousView?: IViewDefinition
  // event.params?: Record<string, string>
});
```

---

## Complete Example

```typescript
import { html, DeesElement, customElement } from '@design.estate/dees-element';
import { DeesAppui, IViewActivationContext } from '@design.estate/dees-catalog';

@customElement('my-app')
class MyApp extends DeesElement {
  private appui: DeesAppui;

  async firstUpdated() {
    this.appui = this.shadowRoot.querySelector('dees-appui');

    this.appui.configure({
      branding: {
        logoIcon: 'lucide:briefcase',
        logoText: 'CRM Pro'
      },

      appBar: {
        menuItems: [
          { name: 'File', submenu: [...] },
          { name: 'Edit', submenu: [...] }
        ],
        showSearch: true,
        user: { name: 'Jane Smith', status: 'online' }
      },

      views: [
        {
          id: 'dashboard',
          name: 'Dashboard',
          iconName: 'lucide:home',
          content: 'crm-dashboard',
          route: 'dashboard'
        },
        {
          id: 'contacts',
          name: 'Contacts',
          iconName: 'lucide:users',
          content: 'crm-contacts',
          route: 'contacts',
          badge: 42
        },
        {
          id: 'settings',
          name: 'Settings',
          iconName: 'lucide:settings',
          content: 'crm-settings',
          route: 'settings/:section?'
        }
      ],

      mainMenu: {
        sections: [
          { name: 'Main', views: ['dashboard', 'contacts'] }
        ],
        bottomItems: ['settings']
      },

      defaultView: 'dashboard',

      onViewChange: (viewId, view) => {
        console.log(`Navigated to: ${view.name}`);
      },

      onSearch: (query) => {
        console.log(`Search: ${query}`);
      }
    });

    // Load activity from backend
    const activities = await fetch('/api/activities').then(r => r.json());
    this.appui.activityLog.addMany(activities);
  }

  render() {
    return html`<dees-appui></dees-appui>`;
  }
}

// View with lifecycle hooks
@customElement('crm-settings')
class CrmSettings extends DeesElement {
  private appui: DeesAppui;

  onActivate(context: IViewActivationContext) {
    this.appui = context.appui;

    // Set secondary menu for settings
    this.appui.setSecondaryMenu({
      heading: 'Settings',
      groups: [
        {
          name: 'Account',
          iconName: 'lucide:user',
          items: [
            { key: 'profile', iconName: 'lucide:user', action: () => this.showSection('profile') },
            { key: 'security', iconName: 'lucide:shield', action: () => this.showSection('security') }
          ]
        },
        {
          name: 'Preferences',
          iconName: 'lucide:settings',
          collapsed: true,
          items: [
            { key: 'notifications', iconName: 'lucide:bell', action: () => this.showSection('notifications') }
          ]
        }
      ]
    });

    // Navigate to section from URL params
    if (context.params?.section) {
      this.showSection(context.params.section);
    }
  }

  showSection(section: string) {
    this.appui.setSecondaryMenuSelection(section);
    // ... load section content
  }
}
```

---

## TypeScript Types

All interfaces are exported from `@design.estate/dees-catalog`:

- `IAppConfig` - Main configuration
- `IViewDefinition` - View definition
- `IViewActivationContext` - Context passed to `onActivate`
- `IViewLifecycle` - Lifecycle hooks interface
- `IViewLifecycleEvent` - Lifecycle event for rxjs Subject
- `IViewChangeEvent` - View change event
- `IAppUser` - User configuration
- `IActivityEntry` - Activity log entry
- `IActivityLogAPI` - Activity log methods
- `IAppBarMenuItem` - App bar menu item
- `IMainMenuConfig` - Main menu configuration
- `ISecondaryMenuGroup` - Secondary menu group
- `ISecondaryMenuItem` - Secondary menu item
- `IMenuItem` - Tab/menu item definition
