# @isa/core/tabs

A sophisticated tab management system for Angular applications providing browser-like navigation with intelligent history management, persistence, and configurable pruning strategies.

## Overview

The Core Tabs library provides a comprehensive solution for managing multiple tabs with navigation history in Angular applications. Built on NgRx Signals, it offers persistent state management, intelligent history pruning, and seamless integration with Angular Router. The system supports unlimited tabs with configurable history limits, automatic browser navigation synchronization, and flexible metadata storage for domain-specific extensions.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Core Concepts](#core-concepts)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [History Pruning](#history-pruning)
- [Configuration](#configuration)
- [Router Integration](#router-integration)
- [Testing](#testing)
- [Architecture Notes](#architecture-notes)

## Features

- **NgRx Signals state management** - Modern functional state with reactive patterns
- **Persistent tab storage** - Automatic state persistence with UserStorageProvider
- **Browser navigation sync** - Automatic Router event synchronization
- **Intelligent history pruning** - Three strategies: oldest, balanced, smart
- **Configurable limits** - Per-tab or global history size configuration
- **Zod validation** - Runtime schema validation for all operations
- **Index validation** - Automatic history index integrity checking
- **Forward/backward navigation** - Browser-style back/forward with pruning awareness
- **Metadata system** - Flexible per-tab metadata storage
- **Angular DevTools integration** - Debug tab state with Redux DevTools
- **Router resolver** - Automatic tab creation from route parameters
- **Injection helpers** - Convenient signal-based injection functions
- **Navigate back component** - Ready-to-use back button component

## Quick Start

### 1. Import and Inject

```typescript
import { Component, inject } from '@angular/core';
import { TabService, injectTab, injectTabId } from '@isa/core/tabs';

@Component({
  selector: 'app-feature',
  template: '...'
})
export class FeatureComponent {
  // Option 1: Inject full service
  #tabService = inject(TabService);

  // Option 2: Inject current tab as signal
  currentTab = injectTab();

  // Option 3: Inject current tab ID as signal
  currentTabId = injectTabId();
}
```

### 2. Create and Activate a Tab

```typescript
createNewTab(): void {
  const tab = this.#tabService.addTab({
    name: 'Customer Details',
    tags: ['customer', 'active'],
    metadata: { customerId: 12345 }
  });

  this.#tabService.activateTab(tab.id);
  console.log(`Created tab ${tab.id} at ${new Date(tab.createdAt)}`);
}
```

### 3. Navigate to a Location

```typescript
navigateToCustomer(customerId: number): void {
  const tabId = this.#tabService.activatedTabId();
  if (!tabId) return;

  this.#tabService.navigateToLocation(tabId, {
    timestamp: Date.now(),
    title: `Customer ${customerId}`,
    url: `/customer/${customerId}`
  });
}
```

### 4. Navigate Back/Forward

```typescript
goBack(): void {
  const tabId = this.#tabService.activatedTabId();
  if (!tabId) return;

  const previousLocation = this.#tabService.navigateBack(tabId);
  if (previousLocation) {
    this.router.navigateByUrl(previousLocation.url);
  }
}

goForward(): void {
  const tabId = this.#tabService.activatedTabId();
  if (!tabId) return;

  const nextLocation = this.#tabService.navigateForward(tabId);
  if (nextLocation) {
    this.router.navigateByUrl(nextLocation.url);
  }
}
```

### 5. Setup Router Integration

```typescript
import { ApplicationConfig } from '@angular/core';
import { TabNavigationService } from '@isa/core/tabs';

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    {
      provide: APP_INITIALIZER,
      useFactory: (tabNav: TabNavigationService) => () => tabNav.init(),
      deps: [TabNavigationService],
      multi: true
    }
  ]
};
```

## Core Concepts

### Tab Entity Structure

Each tab is stored as an NgRx entity with the following structure:

```typescript
interface Tab {
  id: number;                           // Unique identifier
  name: string;                         // Display name
  createdAt: number;                    // Creation timestamp (ms)
  activatedAt?: number;                 // Last activation timestamp
  metadata: Record<string, unknown>;    // Flexible metadata storage
  location: TabLocationHistory;         // Navigation history
  tags: string[];                       // Organization tags
}
```

### Location History

Navigation history is tracked using a cursor-based system:

```typescript
interface TabLocationHistory {
  current: number;                      // Index of current location (-1 for empty)
  locations: TabLocation[];             // Array of visited locations
}

interface TabLocation {
  timestamp: number;                    // Visit timestamp (ms)
  title: string;                        // Page title
  url: string;                          // Full URL
}
```

**Example Navigation Flow:**
```typescript
// Initial state
{ current: -1, locations: [] }

// After navigating to /home
{ current: 0, locations: [{ timestamp: ..., title: 'Home', url: '/home' }] }

// After navigating to /products
{
  current: 1,
  locations: [
    { timestamp: ..., title: 'Home', url: '/home' },
    { timestamp: ..., title: 'Products', url: '/products' }
  ]
}

// After navigating back
{ current: 0, locations: [...] }  // Points back to /home
```

### Tab Metadata

Metadata provides flexible per-tab storage for domain-specific data:

```typescript
// Store customer context
this.#tabService.patchTabMetadata(tabId, {
  customerId: 12345,
  customerName: 'John Doe',
  viewMode: 'details'
});

// Retrieve typed metadata
import { getMetadataHelper } from '@isa/core/tabs';

const customerId = getMetadataHelper(
  tabId,
  'customerId',
  z.number(),
  this.#tabService.entityMap()
);
```

**History Configuration Overrides:**
```typescript
// Override global history limits per tab
this.#tabService.patchTabMetadata(tabId, {
  maxHistorySize: 25,        // Override global limit
  maxForwardHistory: 5       // Custom forward history depth
});
```

### Tab ID Generation

Tab IDs are automatically generated using timestamp-based generation:

```typescript
// Automatic ID generation
const tab = this.#tabService.addTab({
  name: 'New Tab'
  // ID automatically generated: Date.now()
});

// Manual ID specification
const tab = this.#tabService.addTab({
  id: 42,
  name: 'Fixed ID Tab'
});
```

## API Reference

### TabService

NgRx SignalStore providing complete tab management functionality.

#### State Properties

##### `entities(): Tab[]`
Returns array of all tabs in the store.

**Example:**
```typescript
const allTabs = this.#tabService.entities();
console.log(`Total tabs: ${allTabs.length}`);
```

##### `entityMap(): { [id: number]: Tab }`
Returns dictionary mapping tab IDs to tab objects.

**Example:**
```typescript
const tabMap = this.#tabService.entityMap();
const tab42 = tabMap[42];
```

##### `activatedTabId(): number | null`
Returns ID of currently activated tab, or null.

**Example:**
```typescript
const activeId = this.#tabService.activatedTabId();
if (activeId !== null) {
  console.log(`Active tab: ${activeId}`);
}
```

##### `activatedTab(): Tab | null`
Computed signal returning currently activated tab entity.

**Example:**
```typescript
const activeTab = this.#tabService.activatedTab();
if (activeTab) {
  console.log(`Active: ${activeTab.name}`);
}
```

#### Tab Management Methods

##### `addTab(params): Tab`

Creates and adds a new tab to the store.

**Parameters:**
- `params: AddTabInput` - Tab creation parameters (validated with Zod)
  - `name?: string` - Display name (default: 'Neuer Vorgang')
  - `tags?: string[]` - Initial tags (default: [])
  - `metadata?: Record<string, unknown>` - Initial metadata (default: {})
  - `id?: number` - Optional ID (auto-generated if omitted)
  - `activatedAt?: number` - Optional activation timestamp

**Returns:** Created Tab entity with generated ID and timestamps

**Example:**
```typescript
const tab = this.#tabService.addTab({
  name: 'Customer Order #1234',
  tags: ['order', 'customer'],
  metadata: { orderId: 1234, status: 'pending' }
});

console.log(`Created tab ${tab.id}`);
```

##### `activateTab(id: number): void`

Activates a tab by ID and updates its activation timestamp.

**Parameters:**
- `id: number` - Tab ID to activate

**Side Effects:**
- Updates `activatedTabId` state
- Sets tab's `activatedAt` to current timestamp

**Example:**
```typescript
this.#tabService.activateTab(42);
```

##### `patchTab(id: number, changes: PatchTabInput): void`

Partially updates a tab's properties.

**Parameters:**
- `id: number` - Tab ID to update
- `changes: PatchTabInput` - Partial tab updates
  - `name?: string` - Updated display name
  - `tags?: string[]` - Updated tags array
  - `metadata?: Record<string, unknown>` - Metadata to merge
  - `location?: TabLocationHistory` - Updated location history

**Example:**
```typescript
this.#tabService.patchTab(42, {
  name: 'Updated Name',
  tags: ['new-tag'],
  metadata: { additionalField: 'value' }
});
```

##### `patchTabMetadata(id: number, metadata: Record<string, unknown>): void`

Updates tab metadata by merging with existing metadata.

**Parameters:**
- `id: number` - Tab ID to update
- `metadata: Record<string, unknown>` - Metadata to merge

**Example:**
```typescript
// Existing metadata: { customerId: 123 }
this.#tabService.patchTabMetadata(42, {
  orderCount: 5
});
// Result: { customerId: 123, orderCount: 5 }
```

##### `removeTab(id: number): void`

Removes a tab from the store.

**Parameters:**
- `id: number` - Tab ID to remove

**Example:**
```typescript
this.#tabService.removeTab(42);
```

#### Navigation Methods

##### `navigateToLocation(id: number, location: TabLocationInput): TabLocation | null`

Navigates to a new location in tab history with intelligent pruning.

**Parameters:**
- `id: number` - Tab ID to navigate
- `location: TabLocationInput` - Location to add
  - `timestamp: number` - Visit timestamp (ms)
  - `title: string` - Page title
  - `url: string` - Full URL

**Returns:** Validated TabLocation, or null if tab not found

**Behavior:**
1. Prunes forward history based on `maxForwardHistory`
2. Adds new location after current position
3. Applies history size pruning if needed
4. Validates and corrects history index

**Example:**
```typescript
this.#tabService.navigateToLocation(42, {
  timestamp: Date.now(),
  title: 'Product Details',
  url: '/products/123'
});
```

##### `navigateBack(id: number): TabLocation | null`

Navigates backward in tab history.

**Parameters:**
- `id: number` - Tab ID to navigate

**Returns:** Previous location, or null if at start or tab not found

**Example:**
```typescript
const previousLocation = this.#tabService.navigateBack(42);
if (previousLocation) {
  this.router.navigateByUrl(previousLocation.url);
}
```

##### `navigateForward(id: number): TabLocation | null`

Navigates forward in tab history.

**Parameters:**
- `id: number` - Tab ID to navigate

**Returns:** Next location, or null if at end or tab not found

**Example:**
```typescript
const nextLocation = this.#tabService.navigateForward(42);
if (nextLocation) {
  this.router.navigateByUrl(nextLocation.url);
}
```

##### `getCurrentLocation(id: number): TabLocation | null`

Gets current location with automatic index validation.

**Parameters:**
- `id: number` - Tab ID to query

**Returns:** Current location, or null if none or invalid

**Side Effects:**
- Automatically corrects invalid history indices
- Logs warnings if index correction occurs (when enabled)

**Example:**
```typescript
const currentLoc = this.#tabService.getCurrentLocation(42);
if (currentLoc) {
  console.log(`Current page: ${currentLoc.title}`);
}
```

##### `updateCurrentLocation(id: number, updates: Partial<TabLocation>): TabLocation | null`

Updates the current location in-place.

**Parameters:**
- `id: number` - Tab ID to update
- `updates: Partial<TabLocation>` - Properties to update

**Returns:** Updated location, or null if invalid

**Example:**
```typescript
// Update current location's title
this.#tabService.updateCurrentLocation(42, {
  title: 'Updated Title'
});
```

##### `clearLocationHistory(id: number): void`

Clears all navigation history for a tab.

**Parameters:**
- `id: number` - Tab ID to clear

**Example:**
```typescript
this.#tabService.clearLocationHistory(42);
// Result: { current: -1, locations: [] }
```

### TabNavigationService

Service that automatically synchronizes Angular Router navigation with tab history.

#### `init(): void`

Initializes automatic navigation synchronization.

**Behavior:**
- Subscribes to Router NavigationEnd events
- Updates tab location history on each navigation
- Handles browser back/forward with pruning awareness
- Respects URL blacklist configuration

**Usage:**
```typescript
// In app config
{
  provide: APP_INITIALIZER,
  useFactory: (tabNav: TabNavigationService) => () => tabNav.init(),
  deps: [TabNavigationService],
  multi: true
}
```

#### `syncCurrentRoute(): void`

Manually syncs current route to active tab's history.

**Use Cases:**
- Initial page loads
- New tab creation
- Manual synchronization after route changes
- Recovery scenarios

**Example:**
```typescript
// After creating a new tab
const tab = this.#tabService.addTab({ name: 'New Tab' });
this.#tabService.activateTab(tab.id);
this.#tabNavigationService.syncCurrentRoute();
```

### Helper Functions

#### `injectTab(): Signal<Tab | null>`

Injects current activated tab as a signal.

**Returns:** Signal emitting active tab or null

**Example:**
```typescript
export class MyComponent {
  currentTab = injectTab();

  ngOnInit() {
    effect(() => {
      const tab = this.currentTab();
      console.log(`Active tab: ${tab?.name}`);
    });
  }
}
```

#### `injectTabId(): Signal<number | null>`

Injects current tab ID as a signal.

**Returns:** Signal emitting active tab ID or null

**Example:**
```typescript
export class MyComponent {
  currentTabId = injectTabId();

  isTabActive = computed(() => this.currentTabId() !== null);
}
```

#### `getTabHelper(tabId: number, entities: Record<number, Tab>): Tab | undefined`

Retrieves a tab from entity map.

**Parameters:**
- `tabId: number` - Tab ID to retrieve
- `entities: Record<number, Tab>` - Entity map from TabService

**Returns:** Tab entity or undefined

**Example:**
```typescript
const tab = getTabHelper(42, this.#tabService.entityMap());
```

#### `getMetadataHelper<T>(tabId, key, schema, entities): T | undefined`

Retrieves typed metadata value with Zod validation.

**Parameters:**
- `tabId: number` - Tab ID to query
- `key: string` - Metadata key to retrieve
- `schema: ZodSchema<T>` - Zod schema for validation
- `entities: EntityMap<Tab>` - Entity map from TabService

**Returns:** Validated metadata value or undefined

**Example:**
```typescript
import { z } from 'zod';

const customerId = getMetadataHelper(
  42,
  'customerId',
  z.number(),
  this.#tabService.entityMap()
);

if (customerId !== undefined) {
  console.log(`Customer ID: ${customerId}`);
}
```

### Router Resolver

#### `tabResolverFn: ResolveFn<Tab>`

Angular Router resolver that ensures tab exists before route activation.

**Behavior:**
- Extracts tabId from route parameter
- Creates tab if it doesn't exist
- Activates the tab
- Returns tab entity for route data

**Route Configuration:**
```typescript
import { Routes } from '@angular/router';
import { tabResolverFn } from '@isa/core/tabs';

export const routes: Routes = [
  {
    path: ':tabId/customer/:customerId',
    component: CustomerComponent,
    resolve: { tab: tabResolverFn }
  }
];
```

**Component Usage:**
```typescript
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-customer',
  template: '...'
})
export class CustomerComponent {
  constructor(private route: ActivatedRoute) {
    const tab = this.route.snapshot.data['tab'];
    console.log(`Resolved tab: ${tab.name}`);
  }
}
```

### NavigateBackButtonComponent

Ready-to-use back button component with automatic state management.

**Selector:** `tabs-navigate-back-button`

**Features:**
- Automatically disables when no back history
- Uses current tab from TabService
- Integrates with Angular Router
- E2E testable with data-what attribute

**Usage:**
```typescript
import { NavigateBackButtonComponent } from '@isa/core/tabs';

@Component({
  selector: 'app-feature',
  imports: [NavigateBackButtonComponent],
  template: `
    <tabs-navigate-back-button />
  `
})
export class FeatureComponent {}
```

## Usage Examples

### Basic Tab Creation and Navigation

```typescript
import { Component, inject } from '@angular/core';
import { TabService } from '@isa/core/tabs';
import { Router } from '@angular/router';

@Component({
  selector: 'app-customer-search',
  template: '...'
})
export class CustomerSearchComponent {
  #tabService = inject(TabService);
  #router = inject(Router);

  openCustomerInNewTab(customerId: number): void {
    // Create new tab with customer context
    const tab = this.#tabService.addTab({
      name: `Customer ${customerId}`,
      tags: ['customer'],
      metadata: { customerId }
    });

    // Activate the new tab
    this.#tabService.activateTab(tab.id);

    // Navigate to customer details
    this.#router.navigate([tab.id, 'customer', customerId]);
  }
}
```

### Working with Tab Metadata

```typescript
import { Component, inject, OnInit } from '@angular/core';
import { TabService, injectTabId, getMetadataHelper } from '@isa/core/tabs';
import { z } from 'zod';

const CustomerMetadataSchema = z.object({
  customerId: z.number(),
  customerName: z.string().optional(),
  viewMode: z.enum(['details', 'orders', 'history']).default('details')
});

type CustomerMetadata = z.infer<typeof CustomerMetadataSchema>;

@Component({
  selector: 'app-customer-details',
  template: '...'
})
export class CustomerDetailsComponent implements OnInit {
  #tabService = inject(TabService);
  tabId = injectTabId();

  ngOnInit() {
    const id = this.tabId();
    if (!id) return;

    // Set initial metadata
    this.#tabService.patchTabMetadata(id, {
      customerId: 12345,
      customerName: 'John Doe',
      viewMode: 'details'
    });
  }

  getCustomerMetadata(): CustomerMetadata | undefined {
    const id = this.tabId();
    if (!id) return undefined;

    return getMetadataHelper(
      id,
      'customerMetadata',
      CustomerMetadataSchema,
      this.#tabService.entityMap()
    );
  }

  switchViewMode(mode: 'details' | 'orders' | 'history'): void {
    const id = this.tabId();
    if (!id) return;

    this.#tabService.patchTabMetadata(id, { viewMode: mode });
  }
}
```

### Custom History Configuration

```typescript
import { Component, inject } from '@angular/core';
import { TabService } from '@isa/core/tabs';

@Component({
  selector: 'app-wizard',
  template: '...'
})
export class WizardComponent {
  #tabService = inject(TabService);

  createWizardTab(): void {
    const tab = this.#tabService.addTab({
      name: 'Order Wizard',
      tags: ['wizard'],
      metadata: {
        // Override global history limits for wizard
        maxHistorySize: 10,      // Keep only last 10 steps
        maxForwardHistory: 0     // Disable forward navigation (wizard-like)
      }
    });

    this.#tabService.activateTab(tab.id);
  }

  // Forward navigation disabled by maxForwardHistory: 0
  // User must complete wizard steps linearly
}
```

### Browser-Style Navigation

```typescript
import { Component, inject, computed } from '@angular/core';
import { TabService } from '@isa/core/tabs';
import { Router } from '@angular/router';

@Component({
  selector: 'app-navigation-controls',
  template: `
    <button
      [disabled]="!canGoBack()"
      (click)="goBack()"
      data-what="back-button">
      Back
    </button>
    <button
      [disabled]="!canGoForward()"
      (click)="goForward()"
      data-what="forward-button">
      Forward
    </button>
  `
})
export class NavigationControlsComponent {
  #tabService = inject(TabService);
  #router = inject(Router);

  canGoBack = computed(() => {
    const tabId = this.#tabService.activatedTabId();
    if (!tabId) return false;

    const tab = this.#tabService.entityMap()[tabId];
    return tab ? tab.location.current > 0 : false;
  });

  canGoForward = computed(() => {
    const tabId = this.#tabService.activatedTabId();
    if (!tabId) return false;

    const tab = this.#tabService.entityMap()[tabId];
    if (!tab) return false;

    return tab.location.current < tab.location.locations.length - 1;
  });

  goBack(): void {
    const tabId = this.#tabService.activatedTabId();
    if (!tabId) return;

    const previousLocation = this.#tabService.navigateBack(tabId);
    if (previousLocation) {
      this.#router.navigateByUrl(previousLocation.url);
    }
  }

  goForward(): void {
    const tabId = this.#tabService.activatedTabId();
    if (!tabId) return;

    const nextLocation = this.#tabService.navigateForward(tabId);
    if (nextLocation) {
      this.#router.navigateByUrl(nextLocation.url);
    }
  }
}
```

### Tab Filtering and Organization

```typescript
import { Component, inject, computed } from '@angular/core';
import { TabService } from '@isa/core/tabs';

@Component({
  selector: 'app-tab-manager',
  template: '...'
})
export class TabManagerComponent {
  #tabService = inject(TabService);

  // Filter tabs by tag
  customerTabs = computed(() => {
    return this.#tabService.entities().filter(tab =>
      tab.tags.includes('customer')
    );
  });

  orderTabs = computed(() => {
    return this.#tabService.entities().filter(tab =>
      tab.tags.includes('order')
    );
  });

  // Sort tabs by last activation
  recentTabs = computed(() => {
    return [...this.#tabService.entities()]
      .filter(tab => tab.activatedAt !== undefined)
      .sort((a, b) => (b.activatedAt ?? 0) - (a.activatedAt ?? 0))
      .slice(0, 5);
  });

  // Close all tabs with specific tag
  closeCustomerTabs(): void {
    this.customerTabs().forEach(tab => {
      this.#tabService.removeTab(tab.id);
    });
  }
}
```

## History Pruning

### Pruning Strategies

The library provides three intelligent pruning strategies to manage memory usage while preserving navigation functionality.

#### 1. Oldest First Strategy

Removes oldest history entries when size limit exceeded.

**Use Case:** Simple applications with linear navigation

**Behavior:**
- FIFO (First In, First Out) removal
- Preserves recent navigation
- Maintains current position

**Example:**
```typescript
// With locations [A, B, C, D, E], current=2 (C), maxHistorySize=3
// Result: [C, D, E] with current=0 (still pointing to C)
```

**Configuration:**
```typescript
import { TAB_CONFIG, createTabConfig } from '@isa/core/tabs';

{
  provide: TAB_CONFIG,
  useValue: createTabConfig({
    pruningStrategy: 'oldest',
    maxHistorySize: 50
  })
}
```

#### 2. Balanced Strategy (Default)

Maintains balanced window around current position.

**Use Case:** Most applications with forward/backward navigation

**Behavior:**
- Preserves 70% of entries before current position
- Preserves 30% of entries after current position
- Maintains navigation context around current location

**Example:**
```typescript
// With current=5 in 20 locations, maxHistorySize=10
// Keeps ~7 entries before current, current entry, ~2 entries after
// Result preserves useful navigation context
```

**Configuration:**
```typescript
{
  provide: TAB_CONFIG,
  useValue: createTabConfig({
    pruningStrategy: 'balanced',  // Default
    maxHistorySize: 50
  })
}
```

#### 3. Smart Strategy

Intelligent pruning based on recency and proximity.

**Use Case:** Complex applications with varied navigation patterns

**Behavior:**
- Scores each location by recency and proximity to current
- Recent locations (< 1 hour): 100 points base
- Medium age (< 1 day): 60 points base
- Old locations (> 1 day): 20 points base
- Proximity bonus: 100 - (distance_from_current * 10)
- Keeps highest-scoring locations

**Example:**
```typescript
// Preserves:
// - Recently visited pages (high recency score)
// - Pages near current position (high proximity score)
// - Removes old, distant entries first
```

**Configuration:**
```typescript
{
  provide: TAB_CONFIG,
  useValue: createTabConfig({
    pruningStrategy: 'smart',
    maxHistorySize: 50
  })
}
```

### Forward History Limiting

Controls maximum forward history depth when adding new locations.

**Purpose:** Prevents unlimited "redo" depth while maintaining reasonable navigation

**Default:** 10 forward entries

**Behavior:**
```typescript
// With current=2, maxForwardHistory=2
// Before: [A, B, C, D, E, F]
//               ^
// After:  [A, B, C, D, E]
//               ^
// Preserved 2 forward entries (D, E), removed F
```

**Configuration:**
```typescript
// Global configuration
{
  provide: TAB_CONFIG,
  useValue: createTabConfig({
    maxForwardHistory: 5  // Limit forward history
  })
}

// Per-tab override
this.#tabService.patchTabMetadata(tabId, {
  maxForwardHistory: 0  // Disable forward history (wizard-like)
});
```

### Index Validation

Automatic validation and correction of history indices.

**Purpose:** Maintains data integrity after history modifications

**Behavior:**
- Validates indices on all navigation operations
- Corrects invalid indices automatically
- Logs warnings when corrections occur (configurable)

**Example:**
```typescript
// Invalid index: current=10 with only 5 locations
// Automatically corrected to: current=4 (last valid index)

// Empty history: locations=[], current=5
// Automatically corrected to: current=-1
```

**Configuration:**
```typescript
{
  provide: TAB_CONFIG,
  useValue: createTabConfig({
    enableIndexValidation: true,  // Default: true
    logPruning: true              // Log correction warnings
  })
}
```

## Configuration

### TabConfig Interface

```typescript
interface TabConfig {
  maxHistorySize: number;              // Max history entries (default: 50)
  maxForwardHistory: number;           // Max forward entries (default: 10)
  pruningStrategy: 'oldest' | 'balanced' | 'smart';  // Default: 'balanced'
  enableIndexValidation: boolean;      // Default: true
  logPruning: boolean;                 // Default: false
}
```

### Global Configuration

```typescript
import { ApplicationConfig } from '@angular/core';
import { TAB_CONFIG, createTabConfig } from '@isa/core/tabs';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: TAB_CONFIG,
      useValue: createTabConfig({
        maxHistorySize: 100,
        maxForwardHistory: 20,
        pruningStrategy: 'smart',
        enableIndexValidation: true,
        logPruning: true
      })
    }
  ]
};
```

### Per-Tab Configuration Override

```typescript
// Override global limits for specific tab
this.#tabService.addTab({
  name: 'High History Tab',
  metadata: {
    maxHistorySize: 200,      // Override global limit
    maxForwardHistory: 50     // Override global limit
  }
});

// Runtime override
this.#tabService.patchTabMetadata(tabId, {
  maxHistorySize: 25,
  maxForwardHistory: 5
});
```

### Default Configuration

```typescript
const DEFAULT_TAB_CONFIG: TabConfig = {
  maxHistorySize: 50,
  maxForwardHistory: 10,
  pruningStrategy: 'balanced',
  enableIndexValidation: true,
  logPruning: false
};
```

## Router Integration

### Automatic Navigation Synchronization

The TabNavigationService automatically syncs Router events to tab history.

**Setup:**
```typescript
import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { TabNavigationService } from '@isa/core/tabs';

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: (tabNav: TabNavigationService) => () => tabNav.init(),
      deps: [TabNavigationService],
      multi: true
    }
  ]
};
```

**Behavior:**
- Listens to NavigationEnd events
- Extracts tabId from URL patterns (/:tabId/... or /kunde/:processId/...)
- Creates location entries automatically
- Handles browser back/forward with pruning awareness
- Respects URL blacklist for excluded routes

### URL Patterns

The service recognizes two URL patterns:

**Modern Pattern:**
```typescript
// URL: /42/customer/123
// Extracted tabId: 42
```

**Legacy Pattern:**
```typescript
// URL: /kunde/42/details
// Extracted processId (maps to tabId): 42
```

**Fallback:**
```typescript
// URL: /dashboard (no ID in URL)
// Uses currently activated tab ID
```

### URL Blacklist

Certain URLs are excluded from history tracking:

```typescript
export const HISTORY_BLACKLIST_URLS = ['/kunde/dashboard'];
```

### Router Resolver Integration

```typescript
import { Routes } from '@angular/router';
import { tabResolverFn } from '@isa/core/tabs';

export const routes: Routes = [
  {
    path: ':tabId',
    resolve: { tab: tabResolverFn },
    children: [
      {
        path: 'customer/:customerId',
        component: CustomerComponent
      },
      {
        path: 'order/:orderId',
        component: OrderComponent
      }
    ]
  }
];
```

**Resolver Behavior:**
1. Extracts tabId from route parameter
2. Validates tabId is positive integer
3. Creates tab if it doesn't exist (name: 'Neuer Vorgang')
4. Activates the tab
5. Returns tab entity for route data access

## Testing

The library uses **Jest** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test core-tabs --skip-nx-cache

# Run tests with coverage
npx nx test core-tabs --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test core-tabs --watch
```

### Test Structure

The library would benefit from comprehensive unit tests covering:

- **Tab creation** - Add, activate, remove operations
- **Navigation** - Forward, backward, location management
- **History pruning** - All three strategies with various scenarios
- **Index validation** - Correction logic and edge cases
- **Metadata management** - Patch, retrieve, type validation
- **Router integration** - Navigation sync, blacklist, resolver
- **State persistence** - Storage integration
- **Configuration** - Global and per-tab overrides

### Example Test (Recommended)

```typescript
import { TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach } from '@jest/globals';
import { TabService } from './tab';

describe('TabService', () => {
  let service: TabService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(TabService);
  });

  it('should create tab with auto-generated ID', () => {
    const tab = service.addTab({ name: 'Test Tab' });

    expect(tab.id).toBeGreaterThan(0);
    expect(tab.name).toBe('Test Tab');
    expect(tab.location.current).toBe(-1);
    expect(tab.location.locations).toEqual([]);
  });

  it('should navigate to location', () => {
    const tab = service.addTab({ name: 'Test' });

    const location = service.navigateToLocation(tab.id, {
      timestamp: Date.now(),
      title: 'Home',
      url: '/home'
    });

    expect(location).not.toBeNull();
    expect(location?.url).toBe('/home');

    const updatedTab = service.entityMap()[tab.id];
    expect(updatedTab.location.current).toBe(0);
    expect(updatedTab.location.locations).toHaveLength(1);
  });

  it('should navigate back', () => {
    const tab = service.addTab({ name: 'Test' });

    service.navigateToLocation(tab.id, {
      timestamp: Date.now(),
      title: 'Home',
      url: '/home'
    });

    service.navigateToLocation(tab.id, {
      timestamp: Date.now(),
      title: 'Products',
      url: '/products'
    });

    const previousLocation = service.navigateBack(tab.id);

    expect(previousLocation?.url).toBe('/home');

    const updatedTab = service.entityMap()[tab.id];
    expect(updatedTab.location.current).toBe(0);
  });
});
```

## Architecture Notes

### Current Architecture

The library follows a layered architecture with NgRx Signals at its core:

```
Components/Features
       ↓
  Injection Helpers (injectTab, injectTabId)
       ↓
  TabNavigationService (Router sync)
       ↓
  TabService (NgRx SignalStore)
       ↓
├─→ TabHistoryPruner (history management)
├─→ Storage Integration (UserStorageProvider)
├─→ DevTools Integration (Redux DevTools)
└─→ Zod Validation (schemas)
```

### Design Patterns

#### 1. Signal Store Pattern

Uses NgRx Signals with functional composition:

```typescript
signalStore(
  withDevtools('TabService'),
  withStorage('tabs', UserStorageProvider, { autosave: true }),
  withState({ activatedTabId: null }),
  withEntities<Tab>(),
  withProps(),
  withComputed(),
  withMethods()
)
```

**Benefits:**
- Reactive state management
- Automatic persistence
- DevTools integration
- Type-safe operations

#### 2. Strategy Pattern (Pruning)

Three interchangeable pruning strategies:

```typescript
switch (config.pruningStrategy) {
  case 'oldest': return TabHistoryPruner.pruneOldestFirst(...);
  case 'balanced': return TabHistoryPruner.pruneBalanced(...);
  case 'smart': return TabHistoryPruner.pruneSmart(...);
}
```

**Benefits:**
- Configurable behavior
- Easy testing
- New strategies can be added

#### 3. Injection Token Pattern

Dependency injection for configuration and ID generation:

```typescript
export const TAB_CONFIG = new InjectionToken<TabConfig>('TAB_CONFIG', {
  providedIn: 'root',
  factory: () => DEFAULT_TAB_CONFIG
});

export const CORE_TAB_ID_GENERATOR = new InjectionToken<() => number>(
  'CORE_TAB_ID_GENERATOR',
  { providedIn: 'root', factory: () => generateTabId }
);
```

**Benefits:**
- Easy testing with mocks
- Runtime configuration
- No circular dependencies

### State Persistence

Automatic state persistence using `withStorage()`:

```typescript
withStorage('tabs', UserStorageProvider, { autosave: true })
```

**Features:**
- Saves to user-scoped storage
- Automatic save on state changes
- Loads on service initialization
- Validates loaded state with Zod

**Storage Key:** `tabs` (namespace for user storage)

### Index Integrity

The system maintains history index integrity through:

1. **Validation on read** - `getCurrentLocation()` validates indices
2. **Validation on navigation** - Back/forward check bounds
3. **Correction on errors** - Invalid indices auto-corrected
4. **Logging** - Optional warnings for debugging

**Example Correction:**
```typescript
// Corrupted state: current=10, locations.length=5
const { index, wasInvalid } = TabHistoryPruner.validateLocationIndex(
  locations,
  current
);
// index=4 (corrected to last valid), wasInvalid=true
```

### Performance Considerations

1. **Computed Signals** - `activatedTab` is computed, not stored
2. **Immutable Updates** - Spread operators ensure immutability
3. **Pruning Efficiency** - All strategies are O(n) or better
4. **Index Validation** - O(1) validation, only corrects when needed
5. **Storage Optimization** - Autosave debounced by library

### Known Limitations

#### 1. Tab ID Collision Risk (Low Priority)

**Current State:**
- Uses `Date.now()` for ID generation
- Collision possible with rapid tab creation

**Proposed Solution:**
- Add sequence counter: `Date.now() + (counter++)`
- Or use UUID/nanoid for guaranteed uniqueness

**Impact:** Low risk in practice, rapid creation uncommon

#### 2. Storage Size Limits (Medium Priority)

**Current State:**
- No limit on number of tabs
- Storage could grow unbounded

**Proposed Solutions:**
- Maximum tab limit (configurable)
- Automatic cleanup of old inactive tabs
- Storage quota monitoring

**Impact:** Medium risk for long-running sessions

#### 3. URL Blacklist Not Configurable (Low Priority)

**Current State:**
- Hardcoded blacklist in constants
- Cannot customize at runtime

**Proposed Solution:**
- Injection token for blacklist
- Merge with default blacklist

**Impact:** Low, blacklist rarely needs customization

### Future Enhancements

Potential improvements identified:

1. **Tab Groups** - Organize tabs into collapsible groups
2. **Tab Pinning** - Pin important tabs to prevent closure
3. **Tab Duplication** - Clone existing tabs with history
4. **History Export** - Export/import tab state for debugging
5. **Analytics** - Track tab usage patterns
6. **Lazy History** - Only load history when needed
7. **Tab Sorting** - Custom sort orders (recent, alphabetical, manual)
8. **Search** - Search across tab history locations

## Dependencies

### Required Libraries

- `@angular/core` - Angular framework
- `@angular/router` - Router integration
- `@angular/platform-browser` - Title service
- `@ngrx/signals` - State management
- `@ngrx/signals/entities` - Entity management
- `@angular-architects/ngrx-toolkit` - DevTools integration
- `@isa/core/storage` - Storage providers
- `@isa/core/logging` - Logging service
- `@isa/ui/buttons` - Button component (for NavigateBackButtonComponent)
- `@isa/icons` - Icon library (for NavigateBackButtonComponent)
- `@ng-icons/core` - Icon framework (for NavigateBackButtonComponent)
- `@angular/cdk/coercion` - Array coercion utilities
- `zod` - Schema validation

### Path Alias

Import from: `@isa/core/tabs`

## License

Internal ISA Frontend library - not for external distribution.
