docs(spec): core-package generator + template slimming design
6-phase design for slimming the default template to a minimal kernel and providing turbo generators that scaffold optional core packages (realtime, events, trpc, ui) back when needed. Phase 0 introduces a BindContext object in core-shared with bounded generics over minimal protocol types; optional packages keep their full interfaces but extends-link to the protocols. This lets feature binders take a single ctx arg and lets optional packages disappear without breaking typechecks. Phases 1-5 ship the generator framework + per- package templates + per-package removal from main. Companion ADR will be assigned at implementation time (expected ADR-017). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
# Core-package generator + template slimming — Design
|
||||
|
||||
**Date:** 2026-05-09
|
||||
**Status:** Draft (pending user review)
|
||||
**Companion ADR:** TBD (numbering tracked at implementation time; expected ADR-017).
|
||||
**Builds on:** ADR-008 (per-feature DI containers), ADR-014 (instrumentation), ADR-015 (events and jobs), ADR-016 (realtime layer).
|
||||
|
||||
---
|
||||
|
||||
## 1. Context and motivation
|
||||
|
||||
This monorepo is intended as a reusable starter template for new projects. Today every new project inherits **all** core packages whether or not it needs them — including `core-realtime` (Socket.IO), `core-events` (cross-feature event bus), `core-trpc` (tRPC server setup), and `core-ui` (design system). For projects that don't ship realtime, don't need cross-feature events, prefer plain server actions over tRPC, or import their design system from outside, those packages are dead weight.
|
||||
|
||||
We want the template's defaults to be the **minimal viable kernel** and provide turbo generators that **scaffold the optional packages back** when a project needs them. The generators take their templates from the *current* shipped packages, so a generator-scaffolded `core-realtime` is byte-identical to today's `packages/core-realtime/`.
|
||||
|
||||
Two architectural realities make this non-trivial:
|
||||
|
||||
- **Feature binders thread cross-cutting deps positionally.** Every feature's `bindProductionX` / `bindDevSeedX` takes a 7-arg positional signature including `bus, queue, realtime, realtimeRegistry`. Removing core-events or core-realtime breaks every feature's signature.
|
||||
- **Optional packages own contracts that other code depends on at compile time.** `IEventBus` and `IRealtimeBroadcaster` are referenced in feature bind files. Their absence breaks typechecking unless something else owns the type.
|
||||
|
||||
Phase 0 of this design solves both via a `BindContext` object with **minimal protocol types in `core-shared`** and **full interfaces remaining in optional packages**. After Phase 0, removing an optional package is a leaf operation: typing falls back to the protocol default, runtime guards (`ctx.realtime?.broadcast(...)`) handle absence.
|
||||
|
||||
## 2. Decision summary
|
||||
|
||||
1. Introduce a `BindContext` type in `core-shared` with required (`tracer`, `logger`, `config`) and optional (`bus`, `queue`, `realtime`, `realtimeRegistry`) fields. Optional fields use **bounded generics** over minimal protocol types (`EventBusProtocol`, `RealtimeBroadcasterProtocol`, `RealtimeRegistryProtocol`) defined in `core-shared`.
|
||||
2. Optional packages keep their full interfaces (`IEventBus`, `IRealtimeBroadcaster`, etc.) but declare them `extends` the corresponding protocol — typechecks fail if a refactor breaks the protocol surface.
|
||||
3. Convert all 5 features' `bindProduction<X>` / `bindDevSeed<X>` from positional args to single-arg `(ctx: BindContext)`. App aggregator binders construct the context once.
|
||||
4. Add `pnpm turbo gen core-package` generator with a `name` prompt selecting one of `{realtime, events, trpc, ui}`. Each name maps to a verbatim template directory + a generator action that scaffolds the package, updates the consuming apps' `transpilePackages` list, and patches `core-eslint/base.js` for any rules that need re-adding.
|
||||
5. Once a generator works, **remove that package from `main`** along with its consuming-app wiring and its ESLint rules (per the user's preference for "remove + re-add"). The `main` template ends in a slimmed state.
|
||||
6. Per-package documentation (ADR-016, realtime guide, etc.) stays in `docs/` permanently with a "Status: optional, scaffolded via `gen core-package <name>`" header. HTML explainers (`data-flow-explainer.html`, `di-explainer.html`) get conditional/dashed rendering for optional packages.
|
||||
|
||||
## 3. Architecture overview
|
||||
|
||||
Five sequential, independently mergeable phases:
|
||||
|
||||
| Phase | Ships | Verification |
|
||||
|---|---|---|
|
||||
| 0 | `BindContext` type, all binders + app aggregators on single-arg `ctx`, doc + HTML updates for the binder change | Behavior unchanged; all CI gates green |
|
||||
| 1 | `pnpm turbo gen core-package` framework — entry, prompt, dispatch table — but no templates yet | `core-package` registered with empty `choices`; framework no-op |
|
||||
| 2 | core-realtime: capture template → ship generator action → remove from `main` → ESLint rules excised | From slim tree, `gen core-package realtime` produces byte-identical reconstruction |
|
||||
| 3 | core-events: same lifecycle | same |
|
||||
| 4 | core-trpc: same | same |
|
||||
| 5 | core-ui: same | same |
|
||||
|
||||
Plus a final cross-doc sweep + end-to-end "scaffold everything from slim main" acceptance test.
|
||||
|
||||
## 4. Phase 0 — BindContext refactor
|
||||
|
||||
### 4.1 Protocol types in core-shared
|
||||
|
||||
```ts
|
||||
// packages/core-shared/src/di/bind-protocols.ts
|
||||
export type EventBusProtocol = {
|
||||
publish<T>(event: { name: string }, payload: T): Promise<void>;
|
||||
subscribe<T>(
|
||||
event: { name: string },
|
||||
consumer: string,
|
||||
handler: (payload: T) => Promise<void>,
|
||||
): void;
|
||||
};
|
||||
|
||||
export type RealtimeBroadcasterProtocol = {
|
||||
broadcast<T>(
|
||||
channel: { name: string; key?: unknown },
|
||||
payload: T,
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
export type RealtimeRegistryProtocol = {
|
||||
register(entry: { descriptor: unknown; handler: unknown }): void;
|
||||
registerChannel(descriptor: unknown): void;
|
||||
listChannels(): unknown[];
|
||||
};
|
||||
```
|
||||
|
||||
Each protocol captures only what feature binders touch. Adapter-specific methods stay on the full interfaces in optional packages.
|
||||
|
||||
### 4.2 BindContext type
|
||||
|
||||
```ts
|
||||
// packages/core-shared/src/di/bind-context.ts
|
||||
import type { ITracer, ILogger } from "../instrumentation";
|
||||
import type { IJobQueue } from "../jobs";
|
||||
import type {
|
||||
EventBusProtocol,
|
||||
RealtimeBroadcasterProtocol,
|
||||
RealtimeRegistryProtocol,
|
||||
} from "./bind-protocols";
|
||||
|
||||
export type BindContextBase = {
|
||||
tracer: ITracer;
|
||||
logger: ILogger;
|
||||
};
|
||||
|
||||
export type BindContext<
|
||||
Bus extends EventBusProtocol = EventBusProtocol,
|
||||
Realtime extends RealtimeBroadcasterProtocol = RealtimeBroadcasterProtocol,
|
||||
RealtimeReg extends RealtimeRegistryProtocol = RealtimeRegistryProtocol,
|
||||
> = BindContextBase & {
|
||||
bus?: Bus;
|
||||
queue?: IJobQueue;
|
||||
realtime?: Realtime;
|
||||
realtimeRegistry?: RealtimeReg;
|
||||
};
|
||||
|
||||
// Production extends with config; dev-seed does not.
|
||||
export type BindProductionContext<
|
||||
Bus extends EventBusProtocol = EventBusProtocol,
|
||||
Realtime extends RealtimeBroadcasterProtocol = RealtimeBroadcasterProtocol,
|
||||
RealtimeReg extends RealtimeRegistryProtocol = RealtimeRegistryProtocol,
|
||||
> = BindContext<Bus, Realtime, RealtimeReg> & {
|
||||
config: SanitizedConfig;
|
||||
};
|
||||
```
|
||||
|
||||
`SanitizedConfig` comes from `payload` — already a transitive dependency of `core-shared`.
|
||||
|
||||
### 4.3 Optional packages adopt the protocol
|
||||
|
||||
```ts
|
||||
// packages/core-events/src/event-bus.interface.ts (1-line change)
|
||||
import type { EventBusProtocol } from "@repo/core-shared/di/bind-protocols";
|
||||
export interface IEventBus extends EventBusProtocol { /* existing methods */ }
|
||||
```
|
||||
|
||||
```ts
|
||||
// packages/core-realtime/src/realtime-broadcaster.interface.ts (1-line change)
|
||||
import type { RealtimeBroadcasterProtocol } from "@repo/core-shared/di/bind-protocols";
|
||||
export interface IRealtimeBroadcaster extends RealtimeBroadcasterProtocol { /* existing */ }
|
||||
```
|
||||
|
||||
```ts
|
||||
// packages/core-realtime/src/realtime-handler-registry.ts (1-line change to interface)
|
||||
import type { RealtimeRegistryProtocol } from "@repo/core-shared/di/bind-protocols";
|
||||
export interface IRealtimeHandlerRegistry extends RealtimeRegistryProtocol { /* existing */ }
|
||||
```
|
||||
|
||||
The `extends` link forces a typecheck failure if a future refactor breaks the protocol surface — the safety net behind keeping interfaces in optional packages.
|
||||
|
||||
### 4.4 Feature binder migration
|
||||
|
||||
All 10 binders (5 features × `bindProduction` + `bindDevSeed`) get converted from positional to context arg:
|
||||
|
||||
**Before:**
|
||||
|
||||
```ts
|
||||
export function bindProductionAuth(
|
||||
config: SanitizedConfig,
|
||||
tracer: ITracer,
|
||||
logger: ILogger,
|
||||
bus: IEventBus,
|
||||
queue: IJobQueue,
|
||||
realtime: IRealtimeBroadcaster,
|
||||
realtimeRegistry: IRealtimeHandlerRegistry,
|
||||
): void { /* ... */ }
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```ts
|
||||
export function bindProductionAuth(ctx: BindProductionContext): void {
|
||||
const { config, tracer, logger, bus, realtime, realtimeRegistry } = ctx;
|
||||
// body unchanged otherwise; optional fields become guarded:
|
||||
bus?.subscribe(/* ... */);
|
||||
realtimeRegistry?.register(/* ... */);
|
||||
}
|
||||
```
|
||||
|
||||
Same for `bindDevSeedAuth(ctx: BindContext)` (no `config` field).
|
||||
|
||||
### 4.5 App aggregator migration
|
||||
|
||||
`apps/web-next/src/server/bind-production.ts` builds the context once:
|
||||
|
||||
```ts
|
||||
import type { IEventBus } from "@repo/core-events";
|
||||
import type {
|
||||
IRealtimeBroadcaster,
|
||||
IRealtimeHandlerRegistry,
|
||||
} from "@repo/core-realtime";
|
||||
|
||||
const ctx: BindProductionContext<
|
||||
IEventBus,
|
||||
IRealtimeBroadcaster,
|
||||
IRealtimeHandlerRegistry
|
||||
> = {
|
||||
config: resolvedConfig,
|
||||
tracer,
|
||||
logger,
|
||||
bus,
|
||||
queue,
|
||||
realtime,
|
||||
realtimeRegistry,
|
||||
};
|
||||
await bindProductionAuth(ctx);
|
||||
await bindProductionBlog(ctx);
|
||||
// ... all five
|
||||
```
|
||||
|
||||
Same in `bindAllDevSeed` (no `config`).
|
||||
|
||||
**Generic args evolve per phase.** The example above assumes core-events and core-realtime are present (Phase 0 state). After Phase 2 (core-realtime removal), the imports and the second/third generic args are dropped — `BindProductionContext<IEventBus>` falls back to the protocol defaults for the missing slots. After Phase 3 (core-events removal), it's `BindProductionContext` with no generic args. The generator's per-phase actions edit this aggregator file accordingly.
|
||||
|
||||
### 4.6 Phase 0 file budget
|
||||
|
||||
~12-15 files touched: 2 new in core-shared, 3 minor `extends` changes in optional packages, 10 binder rewrites, 2 app aggregator updates, 1 test file update, doc + HTML refreshes.
|
||||
|
||||
## 5. Phase 1 — Generator framework
|
||||
|
||||
Smallest possible scaffold; no templates yet.
|
||||
|
||||
```ts
|
||||
// turbo/generators/config.ts (additions)
|
||||
const CORE_PACKAGE_GENERATORS: Record<
|
||||
string,
|
||||
(a: { name: string }) => PlopTypes.ActionType[]
|
||||
> = {
|
||||
// Phases 2-5 each insert one entry here.
|
||||
};
|
||||
|
||||
plop.setGenerator("core-package", {
|
||||
description: "Scaffold an optional core package (realtime, events, trpc, ui)",
|
||||
prompts: [
|
||||
{
|
||||
type: "list",
|
||||
name: "name",
|
||||
message: "Which optional core package?",
|
||||
choices: [], // Phases 2-5 push entries here.
|
||||
},
|
||||
],
|
||||
actions: (answers) => {
|
||||
const a = answers as { name: string };
|
||||
const handler = CORE_PACKAGE_GENERATORS[a.name];
|
||||
if (!handler) throw new Error(`No generator for core-package '${a.name}'`);
|
||||
return handler(a);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Plus:
|
||||
|
||||
- `turbo/generators/templates/core-package/` directory (empty)
|
||||
- `turbo/generators/lib/core-package-utils.ts` — shared helpers: `assertOptionalPackageNotPresent(name)`, `addToTranspilePackages(nextConfigPath, pkgName)`, `splicePluginRulesAt(baseJsPath, anchorName, ruleBlock)`
|
||||
- `turbo/generators/config.test.ts` — confirms `core-package` registered with empty `choices` (regression baseline for phases 2-5)
|
||||
|
||||
No anchor protocol added in this phase. Optional core packages don't have sub-generators; anchors aren't needed beyond what's already used in the existing feature/realtime/event/job generators.
|
||||
|
||||
## 6. Phases 2-5 — Per-package lifecycle
|
||||
|
||||
Each per-package phase follows a 4-step lifecycle:
|
||||
|
||||
1. **Capture template** — copy `packages/<pkg>/**` into `turbo/generators/templates/core-package/<pkg>/` as `.hbs` siblings. Files are verbatim; `.hbs` extension is mechanical (plop requirement). Package name is **not** parameterized — these are concrete preserved templates.
|
||||
|
||||
2. **Generator action** — push entry into Phase 1's dispatch table. Each entry returns the actions array:
|
||||
|
||||
```ts
|
||||
realtime: () => [
|
||||
() => assertOptionalPackageNotPresent("core-realtime"),
|
||||
...emitTemplateTree("core-package/realtime", "packages/core-realtime"),
|
||||
() => addToTranspilePackages("apps/web-next/next.config.mjs", "@repo/core-realtime"),
|
||||
// Phase 2 only — explicit boundaries entry needed for mode: "folder":
|
||||
() => addBoundariesEntry("packages/core-realtime", { mode: "folder" }),
|
||||
// Phase 2 only — re-add ESLint rule files + base.js plugin block:
|
||||
...emitRealtimeESLintRules(),
|
||||
() => splicePluginRulesAt("packages/core-eslint/base.js", "realtime-rules-imports", REALTIME_IMPORTS),
|
||||
() => splicePluginRulesAt("packages/core-eslint/base.js", "realtime-rules", REALTIME_PLUGIN_BLOCK),
|
||||
() => printRealtimeNextSteps(),
|
||||
],
|
||||
```
|
||||
|
||||
3. **Remove from main** — delete the package; remove the consuming-app wiring (`transpilePackages`, app `server.ts` bootstrap, etc.); remove the package's ESLint rules (per "remove + re-add" choice). Anchors stay in `base.js` even when empty.
|
||||
|
||||
4. **Verify byte-identical reconstruction** — from the slim tree, run `pnpm turbo gen core-package <name>`. Compare result against snapshot. Run all CI gates. Must be green.
|
||||
|
||||
"Byte-identical" allows: plop's `.hbs` extension is stripped from emitted file paths; trailing-newline normalization (LF, single trailing newline). Disallows any other content drift. The snapshot stores sha256 of post-normalization content.
|
||||
|
||||
### 6.1 Per-package variations
|
||||
|
||||
| Phase | Package | Special generator actions | Special removal actions |
|
||||
|---|---|---|---|
|
||||
| 2 | core-realtime | Add `mode: "folder"` boundaries entry (placed before the `packages/core-*` wildcard); emit ESLint rules; splice base.js | Strip ESLint rule files + base.js block; strip realtime bootstrap from `apps/web-next/server.ts`; strip realtime params from app aggregator |
|
||||
| 3 | core-events | Splice the inline `no-restricted-syntax` block into base.js for E1 + J | Strip the block from base.js; in `apps/web-next/src/server/bind-production.ts` drop the `bus` and `queue` construction and narrow the `BindProductionContext` generics |
|
||||
| 4 | core-trpc | Mount tRPC root in `apps/web-next` and `apps/web-tanstack` (manual print step — generator doesn't auto-edit app code) | Strip tRPC root mounting from both apps |
|
||||
| 5 | core-ui | Add to Storybook stories list; add to `apps/web-next/next.config.mjs` `transpilePackages`; add to `apps/web-tanstack/vite.config.ts` (workspace package handling) | Strip Storybook story registrations; remove from `apps/web-next/next.config.mjs` and `apps/web-tanstack/vite.config.ts` |
|
||||
|
||||
### 6.2 ESLint rule placement after each removal
|
||||
|
||||
| Phase | After removal: rule files | After removal: base.js |
|
||||
|---|---|---|
|
||||
| 2 | `packages/core-eslint/rules/no-direct-socket-io.{js,test.js}` and `no-realtime-handler-reexport.{js,test.js}` deleted | Imports stripped; `repo-rules` plugin block at end of base.js stripped; anchors `// <gen:realtime-rules-imports>` and `// <gen:realtime-rules>` remain |
|
||||
| 3 | (No separate files) | E1 / J `no-restricted-syntax` blocks stripped; anchor `// <gen:events-rules>` remains |
|
||||
| 4 | n/a | n/a (no trpc-specific lint rules) |
|
||||
| 5 | n/a | n/a (no ui-specific lint rules) |
|
||||
|
||||
### 6.3 Per-phase printed next-steps
|
||||
|
||||
Each generator prints copy-paste-ready manual wiring steps after running. The realtime example:
|
||||
|
||||
```
|
||||
─────────────────────────────────────────────────────────────
|
||||
core-realtime scaffolded into packages/core-realtime/.
|
||||
|
||||
Manual wiring required:
|
||||
|
||||
1. apps/web-next/server.ts — boot Socket.IO + pass into bindAll:
|
||||
import { Server as IOServer } from "socket.io";
|
||||
import {
|
||||
RealtimeHandlerRegistry,
|
||||
SocketIORealtimeBroadcaster,
|
||||
SocketIORealtimeServer,
|
||||
} from "@repo/core-realtime";
|
||||
const io = new IOServer(httpServer);
|
||||
const realtime = new SocketIORealtimeBroadcaster(io);
|
||||
const realtimeRegistry = new RealtimeHandlerRegistry();
|
||||
await bindAll({ realtime, realtimeRegistry });
|
||||
// ...authenticator + SocketIORealtimeServer.start() ...
|
||||
|
||||
2. Feature bind-production.ts files publishing/subscribing via realtime
|
||||
continue to read ctx.realtime / ctx.realtimeRegistry — no changes
|
||||
needed in features (Phase 0 already wired this).
|
||||
|
||||
3. Verify:
|
||||
pnpm install
|
||||
pnpm lint && pnpm typecheck && pnpm test
|
||||
pnpm turbo boundaries
|
||||
─────────────────────────────────────────────────────────────
|
||||
```
|
||||
|
||||
Equivalent print blocks for events / trpc / ui — each tailored to that package's bootstrap requirements.
|
||||
|
||||
## 7. Testing strategy
|
||||
|
||||
Three layers, common to phases 2-5:
|
||||
|
||||
1. **Generator-emit unit test** — calls the generator action and asserts emitted file paths against the template tree. No plop integration required. Lives in `turbo/generators/config.test.ts` (per-phase additions).
|
||||
2. **Generator e2e test** — programmatically runs `turbo gen core-package <name>` against a tmp workspace, then runs `pnpm install + lint + typecheck + test + boundaries` from the tmp workspace. ~10-15s per package; runs in CI.
|
||||
3. **Byte-identical reconstruction test** — compares the post-generator package tree against a snapshot at `turbo/generators/__snapshots__/core-package/<name>.snapshot.json` (file paths + sha256 of each file's contents). Generator output must match the snapshot. Snapshots are committed alongside templates so review catches unintended drift.
|
||||
|
||||
Phase 0 specifically:
|
||||
|
||||
- `core-shared/src/di/bind-protocols.test.ts` — assignability assertion that `IEventBus`, `IRealtimeBroadcaster`, `IRealtimeHandlerRegistry` are assignable to their respective protocols
|
||||
- `apps/web-next/src/server/bind-production.test.ts` — updated to call binders with `ctx` arg
|
||||
- Each feature's `bind-dev-seed.test.ts` — updated to construct `ctx` and pass
|
||||
|
||||
Phase 1 specifically:
|
||||
|
||||
- `turbo/generators/config.test.ts` — asserts `core-package` generator registered with empty `choices` (baseline for later phases)
|
||||
|
||||
End-to-end acceptance test (final, after Phase 5):
|
||||
|
||||
```bash
|
||||
git clone <slimmed-template> /tmp/test-project
|
||||
cd /tmp/test-project
|
||||
pnpm turbo gen core-package realtime
|
||||
pnpm turbo gen core-package events
|
||||
pnpm turbo gen core-package trpc
|
||||
pnpm turbo gen core-package ui
|
||||
pnpm install && pnpm lint && pnpm typecheck && pnpm test && pnpm turbo boundaries
|
||||
# Result must match pre-slim main byte-identical (modulo lockfile order)
|
||||
```
|
||||
|
||||
## 8. Documentation & HTML updates
|
||||
|
||||
Threaded into each phase, plus a final cross-doc sweep.
|
||||
|
||||
| Phase | Doc updates | HTML updates |
|
||||
|---|---|---|
|
||||
| 0 | `docs/architecture/dependency-flow.md` (BindContext shape replaces positional args); `docs/architecture/vertical-feature-spec.md` (binder signature section); per-feature `AGENTS.md` (binder examples); `CLAUDE.md` (binder rule text) | `docs/architecture/di-explainer.html` (DI binder visualization), `docs/architecture/data-flow-explainer.html` (binder param flow) |
|
||||
| 1 | New `docs/scaffolding/core-package-generator.md`; `CLAUDE.md` Quick Start adds `pnpm turbo gen core-package`; `AGENTS.md` mentions the new generator alongside existing four | none |
|
||||
| 2 | ADR-016 gets "Status: optional, scaffolded via `gen core-package realtime`" header; `docs/guides/realtime.md` adds prerequisite note | `data-flow-explainer.html` realtime arrows marked conditional (dashed lines, "optional" tag) |
|
||||
| 3 | ADR-015 same treatment; `docs/guides/events-and-jobs.md` prerequisite note | events/bus arrows in `data-flow-explainer.html` marked conditional |
|
||||
| 4 | tRPC integration docs in feature `AGENTS.md` files note tRPC is optional | tRPC layer in `data-flow-explainer.html` marked conditional |
|
||||
| 5 | `core-ui` README + Storybook docs note ui is optional | UI layer in `data-flow-explainer.html` marked conditional |
|
||||
|
||||
Final sweep (after Phase 5):
|
||||
|
||||
- New file: `docs/architecture/template-tiers.md` — explains must-have / optional / generator-scaffolded model with the canonical list
|
||||
- `README.md` (project root) — short "Optional packages" section near Quick Start with the four scaffold commands
|
||||
- HTML explainers — final pass to confirm conditional/optional markers are visually clear and match the documented model
|
||||
- All "Read first" lists in `CLAUDE.md` and `AGENTS.md` re-checked for broken references
|
||||
|
||||
## 9. Out of scope
|
||||
|
||||
Explicitly NOT in this design:
|
||||
|
||||
1. **A `core-package remove` generator.** Phases 2-5 do removal once each. Downstream forks remove what they don't need by editing files directly.
|
||||
2. **Generator templates for new core packages of arbitrary shape** (the `kind`-prompted generic generator). Captured here for posterity; future ADR.
|
||||
3. **Slimming the must-have set further.** `core-shared`, `core-eslint`, `core-typescript`, `core-testing`, `core-cms`, `core-api` remain unconditionally present.
|
||||
4. **Slimming the feature set.** All 5 features stay; making them opt-in is a separate (much bigger) design.
|
||||
5. **Per-app generators.** This work doesn't touch `apps/cms` or `apps/web-tanstack` beyond transpile-package consistency.
|
||||
6. **Doc relocation automation.** ADRs and guides stay in `docs/` permanently with status headers; the generator does not move documentation files.
|
||||
|
||||
## 10. Related
|
||||
|
||||
- ADR-008 — per-feature DI containers (binder shape rules)
|
||||
- ADR-014 — instrumentation (where `ITracer` / `ILogger` live in core-shared)
|
||||
- ADR-015 — events and jobs (introduces `IEventBus` / `IJobQueue`; this design moves the protocol into core-shared)
|
||||
- ADR-016 — realtime layer (introduces `IRealtimeBroadcaster`; this design moves the protocol into core-shared)
|
||||
- Memory: "Future capability — core-package generator" (`/Users/danijel/.claude/projects/-Users-danijel-Documents-Projects-template-vertical/memory/project_core_package_generator.md`) — original ask
|
||||
Reference in New Issue
Block a user