- 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).
2.6 KiB
2.6 KiB
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)
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)andgetRegistry()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,noteseach 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/registrypage (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.tsregisters three stub modules.- Nav renders entries from the registry, not from a hardcoded list.
/debug/registryshows 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
_coreso 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.