# Module System

> ArchiTools internal architecture reference -- module registry, loader, and lifecycle.

---

## Overview

ArchiTools is composed of discrete **modules**, each representing a self-contained functional unit (e.g., *Devize*, *Oferte*, *Pontaj*). Modules are registered in a central registry, gated behind feature flags, lazily loaded at runtime, and isolated from one another. This design provides three guarantees:

1. **Zero bundle cost** for disabled modules -- they are never imported.
2. **Runtime safety** -- a crash in one module cannot propagate to another.
3. **Clear ownership boundaries** -- each module owns its types, storage, and UI.

---

## Module Definition Model

Every module exposes a `config.ts` at its root that exports a `ModuleConfig` object. This is the module's identity card -- it tells the platform everything it needs to know to register, gate, load, and display the module.

### `ModuleConfig` Interface

```typescript
// src/core/module-registry/types.ts

type ModuleCategory = 'operations' | 'generators' | 'management' | 'tools' | 'ai';
type Visibility = 'all' | 'admin' | 'internal';

interface ModuleConfig {
  id: string;                    // unique kebab-case identifier, e.g. "devize-generator"
  name: string;                  // Romanian display name, e.g. "Generator Devize"
  description: string;           // Romanian description shown in module catalog
  icon: string;                  // Lucide icon name, e.g. "file-spreadsheet"
  route: string;                 // base route path, e.g. "/devize"
  category: ModuleCategory;      // sidebar grouping
  featureFlag: string;           // corresponding key in the feature flag system
  visibility: Visibility;        // audience restriction
  version: string;               // semver, e.g. "1.0.0"
  dependencies?: string[];       // other module IDs this depends on
  storageNamespace: string;      // isolated localStorage/IndexedDB prefix
  navOrder: number;              // sidebar sort order within its category
  tags?: string[];               // capability tags for search/filtering
}
```

### Field Contracts

| Field | Constraint |
|---|---|
| `id` | Must be unique across all modules. Kebab-case. Used as the key in the registry map. |
| `route` | Must start with `/`. Must not collide with another module's route. |
| `featureFlag` | Must have a matching entry in `src/config/flags.ts`. |
| `storageNamespace` | Must be unique. Convention: `architools.[module-id]`. |
| `navOrder` | Lower numbers appear first. Modules in the same category are sorted by this value. |
| `dependencies` | If specified, the platform will refuse to load this module unless all listed module IDs are also enabled and loaded. |

### Example

```typescript
// src/modules/devize-generator/config.ts

import { ModuleConfig } from '@/core/module-registry/types';

export const registraturaConfig: ModuleConfig = {
  id: 'registratura',
  name: 'Registratură',
  description: 'Registru de corespondență multi-firmă cu urmărire documente.',
  icon: 'book-open',
  route: '/registratura',
  category: 'operations',
  featureFlag: 'module.registratura',
  visibility: 'all',
  version: '1.0.0',
  dependencies: [],
  storageNamespace: 'architools.registratura',
  navOrder: 10,
  tags: ['registry', 'documents', 'correspondence'],
};
```

---

## Module Directory Structure

Every module lives under `src/modules/` and follows a strict directory convention:

```
src/modules/[module-name]/
  components/       # React components specific to this module
  hooks/            # Custom hooks encapsulating business logic
  services/         # Data services (CRUD, transformations) using the storage abstraction
  types.ts          # Module-specific TypeScript types and interfaces
  config.ts         # ModuleConfig export (the module's identity)
  index.ts          # Public API barrel export
```

### Rules

- **`components/`** -- Only UI components consumed within this module. If a component is needed by multiple modules, it belongs in `src/shared/components/common/`.
- **`hooks/`** -- Business logic hooks. Must not contain UI rendering. Must not directly call `localStorage` or `IndexedDB` -- use the storage abstraction via services.
- **`services/`** -- All data access goes through services. Services consume the platform's storage abstraction layer (`src/core/storage/`) and must scope all keys under `storageNamespace`.
- **`types.ts`** -- All TypeScript types that are internal to this module. Shared types go in `src/core/*/types.ts`.
- **`config.ts`** -- Exports exactly one `ModuleConfig` object. No side effects.
- **`index.ts`** -- The barrel export. This is the **only** file that other parts of the system (the registry, the loader) may import from. It must export `config` and the lazy-loadable root component.

### Barrel Export Convention

```typescript
// src/modules/registratura/index.ts

export { registraturaConfig as config } from './config';
export { default as RegistraturaModule } from './components/RegistraturaModule';
```

The root component (`RegistraturaModule`) is what gets lazy-loaded and rendered at the module's route.

---

## Module Registry

The central registry lives at `src/config/modules.ts`. It imports every module's config and builds a lookup map.

```typescript
// src/config/modules.ts

import { registraturaConfig } from '@/modules/registratura/config';
import { emailSignatureConfig } from '@/modules/email-signature/config';
import { wordXmlConfig } from '@/modules/word-xml/config';
import { promptGeneratorConfig } from '@/modules/prompt-generator/config';
// ... all module configs

import type { ModuleConfig } from '@/core/module-registry/types';

const moduleConfigs: ModuleConfig[] = [
  registraturaConfig,
  emailSignatureConfig,
  wordXmlConfig,
  promptGeneratorConfig,
  // ... add new modules here
];

/** Map of module ID -> ModuleConfig for O(1) lookups */
export const moduleRegistry: Map<string, ModuleConfig> = new Map(
  moduleConfigs.map((config) => [config.id, config])
);

/** All registered configs, sorted by category then navOrder */
export const allModules: ModuleConfig[] = [...moduleConfigs].sort((a, b) => {
  if (a.category !== b.category) return a.category.localeCompare(b.category);
  return a.navOrder - b.navOrder;
});

/** Get configs for a specific category */
export function getModulesByCategory(category: ModuleConfig['category']): ModuleConfig[] {
  return allModules.filter((m) => m.category === category);
}

/** Validate that all module IDs are unique. Called once at startup in dev mode. */
export function validateRegistry(): void {
  const ids = moduleConfigs.map((m) => m.id);
  const duplicates = ids.filter((id, i) => ids.indexOf(id) !== i);
  if (duplicates.length > 0) {
    throw new Error(`Duplicate module IDs detected: ${duplicates.join(', ')}`);
  }

  const routes = moduleConfigs.map((m) => m.route);
  const duplicateRoutes = routes.filter((r, i) => routes.indexOf(r) !== i);
  if (duplicateRoutes.length > 0) {
    throw new Error(`Duplicate module routes detected: ${duplicateRoutes.join(', ')}`);
  }
}
```

The registry is a static, build-time artifact. It does not perform any dynamic discovery -- every module must be explicitly imported. This is intentional: it guarantees that the dependency graph is fully visible to the bundler for tree-shaking and code-splitting.

---

## Module Loader

The loader is responsible for conditionally importing and rendering a module based on its feature flag status.

### Lazy Loading Strategy

Each module's root component is wrapped in `React.lazy()`. The import only fires when the component is actually rendered, which only happens when the feature flag is enabled.

```typescript
// src/core/module-registry/loader.ts

import React, { Suspense } from 'react';
import type { ModuleConfig } from '@/core/module-registry/types';
import { ModuleErrorBoundary } from '@/shared/components/common/module-error-boundary';
import { ModuleSkeleton } from '@/shared/components/common/module-skeleton';

/** Registry of lazy component factories, keyed by module ID */
const lazyComponents: Record<string, React.LazyExoticComponent<React.ComponentType>> = {
  'registratura': React.lazy(() => import('@/modules/registratura')),
  'email-signature': React.lazy(() => import('@/modules/email-signature')),
  'word-xml': React.lazy(() => import('@/modules/word-xml')),
  'prompt-generator': React.lazy(() => import('@/modules/prompt-generator')),
  // ... add new modules here
};

interface ModuleLoaderProps {
  config: ModuleConfig;
}

export function ModuleLoader({ config }: ModuleLoaderProps) {
  const LazyComponent = lazyComponents[config.id];

  if (!LazyComponent) {
    return <ModuleNotFound moduleId={config.id} />;
  }

  return (
    <ModuleErrorBoundary moduleId={config.id} moduleName={config.name}>
      <Suspense fallback={<ModuleSkeleton moduleName={config.name} />}>
        <LazyComponent />
      </Suspense>
    </ModuleErrorBoundary>
  );
}
```

### Key Mechanics

1. **`React.lazy()`** accepts a function that returns a dynamic `import()`. The import is only executed the first time the component renders.
2. **`Suspense`** shows a skeleton loader while the chunk downloads and parses.
3. **`ModuleErrorBoundary`** catches any rendering error within the module and displays a localized error state without crashing the entire application.

### Why Not Automatic Discovery?

We intentionally avoid filesystem-based auto-discovery (e.g., `import.meta.glob`) because:

- It defeats tree-shaking: the bundler must include every discovered module.
- It hides the dependency graph from code review.
- It makes the build non-deterministic if modules are added/removed from the filesystem.

Explicit registration is a small amount of boilerplate that pays for itself in predictability.

---

## Navigation Auto-Discovery

The sidebar reads directly from the module registry to build its navigation tree. No separate nav configuration file exists.

```typescript
// src/components/navigation/Sidebar.tsx (simplified)

import { allModules } from '@/config/modules';
import { useFeatureFlag } from '@/core/feature-flags';
import { groupBy } from '@/shared/lib/utils';

const CATEGORY_LABELS: Record<string, string> = {
  operations: 'Operatiuni',
  generators: 'Generatoare',
  management: 'Management',
  tools: 'Instrumente',
  ai: 'AI & Automatizari',
};

export function Sidebar() {
  const grouped = groupBy(allModules, (m) => m.category);

  return (
    <nav>
      {Object.entries(CATEGORY_LABELS).map(([category, label]) => {
        const modules = grouped[category] ?? [];
        const visibleModules = modules.filter((m) => {
          const flagEnabled = useFeatureFlag(m.featureFlag);
          return flagEnabled;
        });

        if (visibleModules.length === 0) return null;

        return (
          <SidebarGroup key={category} label={label}>
            {visibleModules.map((m) => (
              <SidebarItem key={m.id} icon={m.icon} href={m.route}>
                {m.name}
              </SidebarItem>
            ))}
          </SidebarGroup>
        );
      })}
    </nav>
  );
}
```

This means:
- Adding a module to the registry and enabling its flag automatically adds it to the sidebar.
- Disabling a flag automatically removes the module from navigation.
- `navOrder` controls position within each category group.
- `visibility` can be checked against the current user's role for further filtering.

---

## Module Isolation Rules

Modules must be isolated from one another. This is enforced by convention and code review.

### What Is Allowed

| From | To | Allowed? |
|---|---|---|
| Module A component | Module A hook | Yes |
| Module A service | `src/lib/storage/` | Yes |
| Module A component | `src/components/shared/` | Yes |
| Module A hook | `src/lib/*` (platform utilities) | Yes |
| Module A | Module B (any file) | **No** |

### What Is Not Allowed

- **Direct imports between modules.** Module A must never `import { something } from '@/modules/module-b/...'`. If two modules need to share data, they do so through a shared service in `src/lib/` or through an event bus.
- **Shared mutable state.** No module may write to a global store that another module reads, unless that store is a platform-level shared service (e.g., user context, notification system).
- **Cross-module storage access.** Each module's `storageNamespace` is its own territory. Module A must never read or write keys prefixed with Module B's namespace.

### Enforcement

- **ESLint rule** (planned): a custom `no-restricted-imports` pattern that forbids `@/modules/*/` imports from within a different module directory.
- **Code review**: any PR that introduces a cross-module import must be flagged.

---

## Module Lifecycle

A module goes through the following stages from registration to active use:

```
1. Registration
   Module config is imported into src/config/modules.ts.
   Module appears in the registry map.

2. Feature Flag Check
   At render time, the navigation system and route handler check
   the module's featureFlag via useFeatureFlag(). If disabled,
   the module is invisible and its code is never loaded.

3. Lazy Load
   When the user navigates to the module's route (and the flag is
   enabled), React.lazy() triggers the dynamic import(). The
   bundler fetches the module's code chunk over the network.

4. Suspense Boundary
   While the chunk loads, the Suspense fallback (ModuleSkeleton)
   is displayed.

5. Error Boundary Setup
   The ModuleErrorBoundary wraps the module. If any error occurs
   during mount or subsequent renders, the boundary catches it.

6. Mount
   The module's root component mounts. It may initialize hooks,
   call services, and read from its storage namespace.

7. Storage Namespace Init
   On first use, the module's services initialize their storage
   keys under the module's storageNamespace prefix. This happens
   lazily -- no storage is allocated until the module actually
   writes data.

8. Active
   The module is fully interactive. It manages its own state
   and communicates with the platform only through shared services.

9. Unmount
   When the user navigates away, the module unmounts normally via
   React's unmount lifecycle. Cleanup happens in useEffect
   return functions as usual.
```

### Diagram

```
Registration ──> Flag Check ──┬──> [disabled] ──> Not loaded, not visible
                              │
                              └──> [enabled] ──> Lazy Load ──> Suspense ──> Mount ──> Active
                                                    │
                                                    └──> [error] ──> Error Boundary UI
```

---

## Error Boundaries

Every module is wrapped in a `ModuleErrorBoundary`. This is a React error boundary class component that:

1. Catches JavaScript errors in the module's component tree.
2. Logs the error with the module's ID for diagnostics.
3. Renders a fallback UI with the module name and a retry button.
4. Does **not** crash the shell (sidebar, header, other modules remain functional).

```typescript
// src/components/ModuleErrorBoundary.tsx

import React from 'react';

interface Props {
  moduleId: string;
  moduleName: string;
  children: React.ReactNode;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ModuleErrorBoundary extends React.Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error(
      `[ModuleError] ${this.props.moduleId}:`,
      error,
      errorInfo.componentStack
    );
    // Future: send to error reporting service
  }

  handleRetry = () => {
    this.setState({ hasError: false, error: null });
  };

  render() {
    if (this.state.hasError) {
      return (
        <div className="flex flex-col items-center justify-center p-8 text-center">
          <h2 className="text-lg font-semibold text-destructive">
            Eroare in modulul &ldquo;{this.props.moduleName}&rdquo;
          </h2>
          <p className="mt-2 text-sm text-muted-foreground">
            A aparut o eroare neasteptata. Incercati din nou sau contactati echipa tehnica.
          </p>
          <button
            onClick={this.handleRetry}
            className="mt-4 rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
          >
            Reincearca
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}
```

---

## How to Add a New Module

Step-by-step checklist for adding a module to ArchiTools. For full code examples, see [MODULE-DEVELOPMENT.md](/docs/guides/MODULE-DEVELOPMENT.md).

### 1. Create the directory structure

```bash
mkdir -p src/modules/[module-name]/{components,hooks,services}
touch src/modules/[module-name]/{types.ts,config.ts,index.ts}
```

### 2. Write `config.ts`

Export a `ModuleConfig` with a unique `id`, `route`, `featureFlag`, and `storageNamespace`.

### 3. Implement the module

Build types, services, hooks, and components inside the module directory.

### 4. Create `index.ts` barrel export

Export `config` and the default root component.

### 5. Register in `src/config/modules.ts`

Import the config and add it to the `moduleConfigs` array.

### 6. Register the lazy component in `src/lib/module-loader.ts`

Add a `React.lazy(() => import(...))` entry keyed by the module ID.

### 7. Add a feature flag in `src/config/flags.ts`

Create a flag with the key matching the module's `featureFlag` field.

### 8. Create the route

Add `src/app/(modules)/[module-name]/page.tsx` that renders the `ModuleLoader`.

### 9. Test

- Verify the module appears in the sidebar when the flag is enabled.
- Verify the module is completely absent (no network requests for its chunk) when the flag is disabled.
- Verify the error boundary catches a thrown error gracefully.
- Verify storage operations use the correct namespace prefix.

---

## Appendix: Module Category Descriptions

| Category | Key | Intended Use |
|---|---|---|
| Operațiuni | `operations` | Day-to-day operational tools (registratura, password vault) |
| Generatoare | `generators` | Document/artifact generators (email signature, Word XML, Word templates) |
| Management | `management` | Registries and asset management (IT inventory, address book, digital signatures) |
| Instrumente | `tools` | Utility tools (mini utilities, tag manager) |
| AI & Automatizări | `ai` | AI-powered features (prompt generator, AI chat) |
