- pnpm 10 workspace + TypeScript strict + ESLint flat + Prettier - CLAUDE.md as canonical brief - docs/tasks/ — 22 task briefs broken out by phase for sub-sessions - docs/decisions/ — ADR scaffold Implements task 01 (repo-init).
73 lines
2.6 KiB
Markdown
73 lines
2.6 KiB
Markdown
# 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<unknown>; // payload for /s/<token>
|
|
};
|
|
```
|
|
|
|
### 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.
|