@@ -64,6 +64,170 @@ port parked at `24.4` across several npm releases is not told its context has mo
6464needs the shipped context hashed rather than its version compared, and the JVM ships no
6565agent-context content — only the manifest reader.
6666
67+ ### Added — FR-040: codegen ownership is the framework story
68+
69+ **MetaObjects does not need a codegen package per framework. It needs the ownership doctrine
70+ it already publishes to be true all the way down** — and in three places it was not. A codegen
71+ library cannot chase frameworks: there are more of them than any library can carry, they turn
72+ over faster than a release line, and each one added is a permanent liability the metamodel —
73+ the actual durable asset — gains nothing from. The shipped agent context already says the
74+ right thing ("treat this as a first-class, expected activity — not an escape hatch"). This is
75+ the release that makes it true. **The bar is not "Next.js works": it is that an agent adopting
76+ onto a stack nobody wrote a recipe for — Svelte, Nuxt, Qwik — reaches a working generator
77+ unaided, and treats having done so as normal.**
78+
79+ **How it was found, and the framing error worth recording.** A cold adoption probe ran the
80+ documented TypeScript quickstart against published `0.24.4` inside a Next.js 16 / React 19 app
81+ on Turbopack with a real Postgres. Its substantive result was **positive and is not in
82+ dispute**: the schema and server tiers work on that stack unmodified — `queriesFile()` takes
83+ `db` as a parameter, so a Server Component calls it with no HTTP hop; `routesFileHono()` is
84+ deps-injected, the shape an App Router Route Handler wants; the full cross-port API contract
85+ held. The probe then reported eight "findings", and **most were mis-framed as defects.** The
86+ default templates target Fastify on Node; that they do not emit Next.js output is the
87+ templates doing what they say. Recording that error is the point — a report that reads a
88+ template mismatch as a product failure will keep proposing framework packages forever. Three
89+ findings survived re-scoring, and they are what shipped.
90+
91+ - **`meta eject <generator>` — the ownership move is now a command.** ADR-0034 had `meta init`
92+ eagerly copy four generators (`entity`, `queries`, `routes`, `barrel`) into
93+ `codegen/generators/` and own them. The other five could be *used* but never *owned*: there
94+ was no supported way to get one's source, at init time or after. `meta eject` is that same
95+ copy operation generalised to every ejectable name in every package, callable at any time —
96+ for a generator you skipped at init, or one a package gained since. `meta eject --list`
97+ names all nine grouped by package (`entity`, `queries`, `routes`, `routes-hono`, `barrel`
98+ from `codegen-ts`; `form` from `codegen-ts-react`; `hooks`, `grid`, `grid-hook` from
99+ `codegen-ts-tanstack`). It never clobbers without `--force`, matching `init`'s rule, and it
100+ reports the import line to paste by **parsing it out of the template's own header** rather
101+ than re-deriving it from the file name — a generator's exported symbol does not follow its
102+ file name (`hooks.ts` exports `tanstackQuery`, `routes-hono.ts` exports `routesFileHono`),
103+ so a derived map would drift from what the template already tells a human to paste.
104+ - **The UI tier became ownable at all.** It was the gap with no workaround: an RSC app needs
105+ `"use client"` at the top of generated form/hook files, and there was no seam to add it —
106+ the choice was use the package's output verbatim or hand-write the tier and leave metadata
107+ behind. Four new reference templates (`form`, `hooks`, `grid`, `grid-hook`) plus a new
108+ `routes-hono`, and the render layer is **promoted to public API** — `renderFormFile`,
109+ `renderHooksFile`, `renderColumnsFile`, `renderGridHookFile`, each a stable
110+ `(entity, ctx) => string` — so an owned generator composes the engine and replaces only the
111+ step its framework disagrees about, instead of copying ~600 lines of package internals out.
112+ `codegen-ts` already exported `renderRoutesFile`; this applies the same pattern to the UI
113+ packages. The reference-template reader is now a per-package factory, so a package that
114+ gains templates later is picked up by `meta eject` with no other change.
115+ - **Every reference template documents its own swap point.** A `targets:` header block on all
116+ nine states what the template targets, when to use it, what it emits, where to change it,
117+ and what it composes with — so an agent retargeting reads the seam out of the file rather
118+ than inferring it. Paired with a new **"Your framework isn't the default — the retargeting
119+ procedure"** section in the `metaobjects-codegen` skill, which is the general answer the
120+ per-framework recipes were standing in for.
121+
122+ - **`clientDirective` — the `"use client"` knob.** Set it and the four generated client
123+ artifacts (form, hooks, columns, grid-hook) get the directive React Server Components
124+ frameworks require; leave it off (the default) and output is byte-identical to before.
125+ It is **config, never a metadata attribute**: the directive is a fact about the
126+ adopter's bundler topology, not about the entity, and registering it would give every
127+ non-TS port vocabulary it can never dispatch on — the `source.rdb @role` mistake that
128+ retired four members in `0.21.0`. It defaults **off** because the directive is only
129+ *required* under RSC and is inert-but-warned-about elsewhere; the asymmetry that would
130+ argue for defaulting on (a runtime error for RSC adopters versus a build warning for
131+ everyone else) is precisely what the rest of this release removes — before it, an RSC
132+ adopter had no seam at all. `<Entity>.meta.ts` is deliberately excluded: it is plain
133+ data imported *by* a client component, and in RSC the boundary is the importing
134+ component, not everything it reaches.
135+
136+ **Docs stop promising what the project does not intend to ship.** `AGENTS.md` and the port
137+ docs described a first-party package per framework as the way to reach a new framework; that
138+ was never the plan and is now stated as the ownership move it actually is. The agent-facing
139+ quickstart in `docs/llms/` had been teaching the **deprecated** `@metaobjectsdev/codegen-ts/generators`
140+ import path, and the ownership one-liner samples in the docs did not type-check — both fixed,
141+ with the samples now compiled.
142+
143+ **`meta init` scaffold honesty.** Four fixes where the scaffold said something untrue about
144+ its own output, the same class the `0.24.4` line was cut for: it stopped eagerly scaffolding
145+ an unwired `routes-hono.ts` (a file nothing imported, presented as if it were live); it now
146+ scaffolds a **throwing** `src/db.ts` stub, so the module the default `routesFile()` imports
147+ exists and fails with an instruction rather than `TS2307`; it names the `.gen-state` manifest
148+ and explains `dbImport` instead of leaving both as unexplained config; and its `fastify`
149+ devDependency is aligned to `runtime-ts`'s peer range, which it contradicted. Separately, the
150+ **TanStack Table v8 requirement is now discoverable** — `@metaobjectsdev/tanstack` bounds the
151+ peer at v8, but nothing told an adopter installing `@tanstack/react-table` themselves.
152+
153+ **One recipe, explicitly a convenience.** [`docs/recipes/nextjs-vercel.md`](docs/recipes/nextjs-vercel.md)
154+ walks the Next.js App Router + Vercel path — the `extStyle: "none"` / `clientDirective: true`
155+ config delta, `routesFileHono()` mounted at `app/api/[[...route]]/route.ts` via `hono/vercel`,
156+ and generated query helpers called straight from a Server Component. It changes no package
157+ file, and it opens by saying the general procedure lives in the `metaobjects-codegen` skill:
158+ it is a shortcut past reasoning an agent could do unaided, which is the only relationship
159+ FR-040 permits it to have. Two of its notes exist because the failure is SILENT — a Server
160+ Component reading the database is not a dynamic signal, so the page prerenders at build and
161+ serves build-time rows forever while looking correct in `next dev`; and `apiPrefix` is baked
162+ into the emitted route path, so a Hono `basePath` on top double-prefixes it.
163+
164+ Design: `docs/superpowers/specs/2026-08-29-fr-040-framework-agnostic-codegen-ownership-design.md`.
165+ Amends [ADR-0034](spec/decisions/ADR-0034-codegen-scaffold-and-own.md).
166+
167+ **Review round.** Five of the fixes above are review findings on FR-040's own first
168+ draft, and one is worth naming because it is the shape this project keeps convicting
169+ itself of: **the five new templates shipped with no equivalence gate.** ADR-0034 makes a
170+ copyable template safe by running it *and* the built-in it was copied from over a fixture
171+ corpus and requiring byte-identical output — and that gate covered only the four
172+ `meta init` scaffolds. Since `src/reference` is excluded from tsconfig, the five new ones
173+ were imported by nothing, executed by nothing and type-checked by nothing: a renamed
174+ engine export, or a drifted `filter` deciding WHICH entities emit, would have reached an
175+ adopter running `meta eject` before it reached a red lane. The tsconfig comment even
176+ asserted the coverage, having been copy-pasted into the two UI packages from the one
177+ where it was true. Every template is now gated in every package, the file SET is
178+ compared as well as the contents (a drifted filter changes what is emitted, not how), and
179+ each gate asserts its own coverage equals `REFERENCE_GENERATOR_NAMES` so the tenth
180+ template cannot repeat this. Also fixed from that round: `meta eject` told you to *paste*
181+ an import that collides with the package import already in the documented config — whose
182+ quiet failure mode is a config that keeps running the PACKAGED generator while you edit
183+ the ejected file — and never named the dependency the ejected file imports, so the
184+ adopter's own `tsc` reported TS2307 on the file the CLI had just said they owned.
185+
186+ **Second review round.** Seven more findings, and the two worth naming share a shape with
187+ the first round's: a change that was RIGHT drew its line one notch too wide, and nothing
188+ could see the difference. **The retargeting split took four ports' `own*()` guidance with
189+ it.** Moving `meta eject` and the `metaobjects.config.ts` keys out of the port-agnostic
190+ `SKILL.md` was correct — a Python project runs `metaobjects gen` and has no eject command,
191+ so those adopters' agents were being handed a procedure their toolchain cannot execute.
192+ But the same move carried off the ADR-0039 section, whose per-port own↔resolving **table
193+ is port-agnostic by construction**: its entire content is the OTHER ports' accessor names,
194+ including the trap that TS `attr()` resolves while Python `attr()` is own. It landed in
195+ `references/typescript.md`, the one page a Java, Python or C# adopter never installs,
196+ while `metaobjects-authoring` still told them to go read it there. And `SKILL.md` closed
197+ by sending every reader to "this skill's `references/` fragment for your server language",
198+ which for four of the five contains no retargeting content at all — a pointer to nothing,
199+ where before the split there had at least been a procedure (a wrong one, which is what the
200+ split fixed). The three port-agnostic sections are back in `SKILL.md`, and the closing
201+ pointer now says what is true per port, including that the other ports have **no** eject
202+ command and owning a generator there means implementing that port's generator interface.
203+
204+ **`meta init` claimed a stub it had just decided not to write.** Its next-steps block was
205+ one static string describing the scaffolded `src/db.ts` in the imperative, printed on every
206+ run — including the `meta init --force` in a project keeping its own config, where the
207+ write is deliberately skipped. Worse was the silence beside it: `wroteScaffoldedConfig` is
208+ only *"no config existed"*, so it is false for the config `init` itself wrote one command
209+ earlier, and a scaffolded project whose `src/db.ts` is deleted lands in the same branch as
210+ a project that owns its config — nothing written, nothing preserved, **nothing said**, and
211+ the next `tsc` reporting `TS2307` on a file the command had just chosen not to restore. It
212+ warns now rather than writing, because dropping a file into a project that owns its config
213+ is the unilateral host-project touch FR-040 §4.4 lists as a defect.
214+
215+ Also from the round: `meta eject` **stated which import a project currently has**, which
216+ it never checks and which is wrong for exactly the four `meta init` scaffolds — the
217+ `--force` re-sync case — so it names the goal and the three branches instead; its
218+ dependency notes could not see a subpath import and reported a **peer-declared package as
219+ missing**, advice that if followed adds the competing physical copy this repo has been
220+ bitten by twice; and **nothing gated `clientDirective` on the generated form**, the one
221+ client artifact the RSC story centres on. That last one is the round's own theme again,
222+ and the proof is sharper than the finding: with the directive dropped from a reference
223+ template alone — the two halves genuinely different — `reference-byte-identical` stays
224+ **green**, because it generates with the knob off, where the call is a no-op and removing
225+ it changes no byte it compares. The equivalence gate cannot see that defect in either
226+ direction. Both UI packages now run the knob against the built-in and the reference
227+ template, and each package's template coverage is a `Record` keyed by ejectable name whose
228+ keys are the proof and whose values are the wiring — so a tenth template is a **compile**
229+ error, not a hand-maintained list that can be edited to claim a gate nobody wrote.
230+
67231### Fixed — eight defects found by adopting the product from scratch, twice
68232
69233Two adoption runs against the published `0.24.4`, docs followed literally and nothing fixed
0 commit comments