# 04 — Module loader & registry ## Goal Implement the module system that everything else hangs off: each module declares a manifest, the loader composes them at startup, and core services iterate the registry instead of hardcoding entity types. ## Why This is the load-bearing extensibility piece. Get this wrong and every later module needs core changes. ## Depends on - 03 (DB) ## Scope ### Module manifest type (`src/modules/_core/module.ts`) ```ts export type ModuleManifest = { id: string; // "calendar", "lists", "notes" name: string; // human-readable nav?: { href: string; label: string; icon?: string }; entities: EntityTypeRegistration[]; // see below dashboardWidgets?: DashboardWidget[]; quickAdds?: QuickAddAction[]; }; export type EntityTypeRegistration = { type: string; // "calendar.event", "lists.item", "notes.note" label: { singular: string; plural: string }; share?: ShareCapabilities; // null = not shareable reminder?: ReminderCapabilities; // null = not remindable search?: SearchAdapter; // null = not searchable resolveUrl: (id: string) => string; // canonical app URL loadForShare?: (id: string) => Promise; // payload for /s/ }; ``` ### Registry (`src/modules/_core/registry.ts`) - `registerModule(manifest)` and `getRegistry()` returning frozen views. - `getEntityType(type)` lookup. - Import-time side-effect free: modules are listed and registered explicitly in `src/modules/index.ts`. ### Loader (`src/modules/index.ts`) - Imports each module's manifest and calls `registerModule`. Order is deterministic. - Stub modules for now: `calendar`, `lists`, `notes` each export an empty-but-valid manifest so the loader has something to register. ### Wiring - Root layout reads the registry to render nav. - A throwaway `/debug/registry` page (dev-only) dumps the loaded registry as JSON. ## Out of scope - Real implementations of share / reminder / search adapters (later phases). - Dynamic plugin loading from disk. Modules are statically imported. ## Acceptance criteria - [ ] `src/modules/index.ts` registers three stub modules. - [ ] Nav renders entries from the registry, not from a hardcoded list. - [ ] `/debug/registry` shows all three module manifests in dev. - [ ] Adding a fourth stub module only requires creating its folder + adding one line to `src/modules/index.ts`. - [ ] All types exported from `_core` so other modules can import without circulars. ## Notes - Frozen objects (`Object.freeze`) on registry exposure are cheap insurance against accidental mutation. - Resist the urge to make this a fancy DI container. A plain map is enough.