Skip to content

Commit 0ef92eb

Browse files
dmealingclaude
andcommitted
docs: consumer feedback from wizardsofodd Phase 2 / 0.6.0 adoption
Mid-adoption survey of MO 0.6.0 by wizardsofodd.com (Cloudflare Worker persistence + template prompts + React rewrite + projections). 0.6.0 is unblocking — no items below are blockers — but each is friction every consumer pays once or recurringly. Ranked by leverage (consumer-pain × audience ÷ effort). Tier 1 (highest ROI, smallest cost, total ~2-3 hours): #1 auto-wire generateRenderHandle() + generatePayloadInterfaces() into a stock generator (currently exported but not in the default `meta gen` pipeline; every consumer writes the same boilerplate) #2 parameterize generated *.queries.ts to accept `db` as an argument (eliminates the runtime-throwing src/db/db.ts stub every Worker consumer ships purely to satisfy typecheck) #3 add expected/ codegen output to template-prompt-simple conformance fixture (docs-as-data; explains the codegen contract without running it) Tier 2 (high value, larger): #4 Vite + Cloudflare Workers recipe in README #5 CHANGELOG.md (currently only RELEASING.md process docs exist) #6 better entity-JSON-shape error messages with path + did-you-mean #7 document camelCase TS vs snake_case SQL mapping Tier 3 (nice-to-have): metaobjects/ dir name override, agent-docs opt-out, template.output codegen, wider react component coverage. Tier 4 (verification, ~10 min spot-checks): confirm meta migrate --dialect d1 wires through end-to-end; confirm 0.5.0's deleteXById D1 response-shape bug is fixed. Branch is `proposal/wizardsofodd-consumer-friction-2026-05-25` — review and convert individual items into worktrees as you prioritize. None block wizardsofodd Phase A. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8faad2a commit 0ef92eb

1 file changed

Lines changed: 346 additions & 0 deletions

File tree

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
# Consumer Feedback: wizardsofodd.com — Friction in 0.6.0
2+
3+
**Status:** Proposal for review
4+
**Date:** 2026-05-25
5+
**Author:** Claude (via Doug) during wizardsofodd Phase 2 / MO 0.6.0 adoption
6+
**Consumer project:** [`wizardsofodd.com`](../../../../wizardsofodd.com) — live Cloudflare Worker
7+
deployment with persistence + template-driven prompts + React UI in flight
8+
9+
## Context
10+
11+
`wizardsofodd.com` is in active mid-adoption of MO 0.6.0 across four surfaces:
12+
**persistence** (D1 via codegen-emitted Drizzle), **template-driven prompts**
13+
(`generateRenderHandle()` + `generatePayloadInterfaces()` from
14+
`@metaobjectsdev/codegen-ts`), **React UI** (Vite-built SPA in Workers Static
15+
Assets), and **projection vocabulary** (declared in metadata, hand-rolled
16+
queries). The full spec + plan lives at:
17+
18+
- `wizardsofodd.com/docs/superpowers/specs/2026-05-25-metaobjects-0.6-mega-design.md`
19+
- `wizardsofodd.com/docs/superpowers/plans/2026-05-25-metaobjects-0.6-mega.md`
20+
21+
Consumer's deployment shape (worth keeping in mind as you weigh fixes):
22+
23+
- **Runtime:** Cloudflare Workers (V8 isolate — no Node-only deps allowed)
24+
- **Persistence:** Cloudflare D1 via `drizzle-orm/d1`
25+
- **Use of MO:** codegen output ONLY (no `@metaobjectsdev/runtime-ts` since
26+
Fastify/Kysely can't run in Workers). Codegen runs at dev/build time;
27+
generated artifacts are committed.
28+
- **Build chain:** `meta gen``vite build``wrangler deploy`.
29+
30+
The good news up front: **0.6.0 is unblocking**. The fixes from 0.5.0
31+
(`field.enum` registration, `--dialect d1`, `generateRenderHandle()`) are real
32+
and consumed. The items below are *friction*, not blockers. Each one is
33+
something a consumer pays a small recurring tax on, that would compound as
34+
the consumer audience widens.
35+
36+
Items are ranked by leverage (consumer-pain × scope-of-affected-consumers ÷
37+
estimated implementation effort).
38+
39+
---
40+
41+
## Tier 1 — Highest leverage, smallest cost
42+
43+
### #1 — Auto-wire `generateRenderHandle()` + `generatePayloadInterfaces()` into a stock generator
44+
45+
**Where:** `server/typescript/packages/codegen-ts/src/generators/` — add a new
46+
generator alongside `entityFile()`, `queriesFile()`, `barrel()`.
47+
48+
**Today:** both helpers are exported from `@metaobjectsdev/codegen-ts/payload-codegen`
49+
(verified at `payload-codegen.ts:78-83` and `:89-106`) and individually work
50+
— but no generator in `generators/` calls them. Consumers wanting typed
51+
`renderXxx()` handles must write a custom `Generator` themselves and add it
52+
to their `metaobjects.config.ts`. From the wizardsofodd plan
53+
(Phase B, Task 5):
54+
55+
```ts
56+
// We have to write this in metaobjects.config.ts to get typed prompt handles
57+
import { generatePayloadInterfaces, generateRenderHandle } from "@metaobjectsdev/codegen-ts";
58+
const promptCodegen: Generator = {
59+
name: "prompt-render",
60+
emit(ctx) {
61+
const payloads = ctx.entities.filter(e => e.kind === "object.value");
62+
const prompts = ctx.entities.filter(e => e.kind === "template.prompt");
63+
return [{
64+
path: "src/render/generated/prompts.ts",
65+
content: [
66+
generatePayloadInterfaces(payloads),
67+
...prompts.map(generateRenderHandle),
68+
].join("\n"),
69+
}];
70+
},
71+
};
72+
```
73+
74+
**Suggested fix:** Export a `promptRender()` (or `renderHandles()`) generator
75+
from `@metaobjectsdev/codegen-ts/generators` that does the above. Consumers
76+
who want typed prompts just add it to their generators list:
77+
78+
```ts
79+
// metaobjects.config.ts (consumer side, after fix)
80+
import { entityFile, queriesFile, barrel, promptRender } from "@metaobjectsdev/codegen-ts/generators";
81+
export default defineConfig({
82+
// ...
83+
generators: [entityFile(), queriesFile(), barrel(), promptRender()],
84+
});
85+
```
86+
87+
**Effort:** ~30 minutes. Both helpers already exist; this is glue + one new
88+
file in `generators/`.
89+
90+
**Consumer impact:** Saves ~20 LOC of identical custom-generator boilerplate
91+
per consumer. More importantly, eliminates a "huh, why isn't this just
92+
working?" moment that takes a new consumer 30+ minutes to figure out (the
93+
docs say `generateRenderHandle()` exists; the default `meta gen` doesn't
94+
seem to use it).
95+
96+
---
97+
98+
### #2 — Parameterize generated `*.queries.ts` to accept `db` as a parameter
99+
100+
**Where:** `server/typescript/packages/codegen-ts/src/templates/queries.ts`
101+
(or equivalent template).
102+
103+
**Today:** generated `Council.queries.ts` does:
104+
```ts
105+
import { db } from "../db";
106+
// ...
107+
export async function findCouncilById(id: string) { return db.select()... }
108+
```
109+
110+
This forces every consumer to ship a `src/db/db.ts` module at the import
111+
path the generated code expects. In a Cloudflare Worker, **the `db` is
112+
per-request** (`drizzle(env.DB)` requires the request-scoped binding) — there
113+
is no module-level `db` to import. So consumers ship a runtime-throwing stub
114+
purely to satisfy the typecheck:
115+
116+
```ts
117+
// wizardsofodd Phase A Task 5 (will exist in every Worker consumer)
118+
export const db = (() => {
119+
throw new Error("type-only stub — use getDb(env) in the handler");
120+
})() as unknown as BaseSQLiteDatabase<"async", { rowsAffected: number }>;
121+
```
122+
123+
This is dead code in the bundle that exists ONLY for typecheck. Every Worker
124+
consumer pays this tax forever.
125+
126+
**Suggested fix:** generate query helpers that accept `db` as their first
127+
parameter:
128+
129+
```ts
130+
// generated Council.queries.ts (after fix)
131+
import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core";
132+
export async function findCouncilById(db: BaseSQLiteDatabase, id: string) {
133+
return db.select()...
134+
}
135+
```
136+
137+
Consumers call them with their per-request `db`:
138+
```ts
139+
const db = drizzle(env.DB, { schema });
140+
const council = await findCouncilById(db, "abc123");
141+
```
142+
143+
This is the standard Drizzle / Kysely / TypeORM pattern in 2026.
144+
145+
**Effort:** 1–2 hours — template change in the queries codegen + updating
146+
docs to show the new call shape. Backward-compatibility could be handled by
147+
shipping both shapes briefly (with the parameterless variant calling a
148+
default-export `db`), or it's a breaking change consumers absorb in 0.7.
149+
150+
**Consumer impact:** Eliminates a dead-code kludge every Worker consumer
151+
ships. Aligns with the dominant ORM-helper pattern in TS — current Drizzle
152+
docs all show parameter-passing.
153+
154+
---
155+
156+
### #3 — Add `expected/` output to the `template-prompt-simple` conformance fixture
157+
158+
**Where:** `fixtures/conformance/template-prompt-simple/`.
159+
160+
**Today:** The fixture has `input/meta.ai.json` and `expected.json` (the
161+
parsed metadata tree). It does NOT have an `expected/` directory showing
162+
what the **codegen** produces. A consumer trying to understand "what does
163+
`template.prompt` look like after `meta gen`?" has no canonical example to
164+
read — they have to run the codegen themselves.
165+
166+
This is partly why my initial recon agent (looking at the metaobjects repo
167+
to scope the wizardsofodd integration) reported "no typed codegen for
168+
prompts in 0.6.0" — there was no expected output file to find. The codegen
169+
IS there (`generateRenderHandle()` works), but invisible to anyone reading
170+
the fixture corpus.
171+
172+
**Suggested fix:** add `fixtures/conformance/template-prompt-simple/expected/prompts.ts`
173+
containing the actual output of `generateRenderHandle()` + `generatePayloadInterfaces()`
174+
applied to the input metadata. Either generated-and-committed, or asserted
175+
to match in the conformance test.
176+
177+
**Effort:** ~20 minutes — generate once, commit, add an assertion. If a
178+
conformance check enforces "what `meta gen` produces equals `expected/`,"
179+
even better (catches codegen regressions).
180+
181+
**Consumer impact:** Docs-as-data. Consumers and AI agents alike can read
182+
the fixture and immediately understand the codegen contract without running
183+
anything.
184+
185+
---
186+
187+
## Tier 2 — High value, somewhat larger
188+
189+
### #4 — Document the Vite + Cloudflare Workers recipe
190+
191+
**Where:** `README.md` or a new `docs/recipes/cloudflare-workers.md`.
192+
193+
**Today:** Consumers wanting to use MO codegen output in a Cloudflare Worker
194+
with a Vite-built React SPA frontend (a very common 2026 deployment shape)
195+
have to figure out for themselves:
196+
- Which packages run in V8 isolates (codegen output ✅; `@metaobjectsdev/runtime-ts` ❌)
197+
- How to integrate Vite output with Workers Static Assets without wiping
198+
hand-curated public files (the `emptyOutDir: false` discipline)
199+
- How to wire `meta gen` into the build chain alongside `vite build` and
200+
`wrangler deploy`
201+
- The dev-only nature of codegen (run locally, commit output, never invoke
202+
at runtime)
203+
204+
**Suggested fix:** a documented recipe section + ideally a tiny example repo
205+
(could even be the wizardsofodd shape, lightly anonymized). The recipe
206+
documents the exact constraints that took us multiple sessions to learn.
207+
208+
**Effort:** ~1 hour for the README section; ~2-3 hours if you want a
209+
demo repo.
210+
211+
**Consumer impact:** Cloudflare Workers is one of the most common edge
212+
deployment targets in 2026. Documenting MO's Worker story explicitly grows
213+
the addressable consumer base.
214+
215+
### #5`CHANGELOG.md` at the monorepo root (or per-package)
216+
217+
**Where:** Monorepo root; per-package optional.
218+
219+
**Today:** Release notes only exist in `docs/RELEASING.md` (process docs).
220+
Consumers — including me, doing the 0.5→0.6 upgrade survey — have to do
221+
archaeology to figure out what changed (compare package versions across
222+
tags, grep for renames, etc.). For the wizardsofodd Phase 2 unblock
223+
specifically, the key questions "did `field.enum` register?" and "did
224+
`--dialect d1` ship?" took an Explore agent ~10 minutes to answer; a
225+
CHANGELOG would have made it 10 seconds.
226+
227+
**Suggested fix:** standard Keep-a-Changelog format. Backfill 0.5.0 → 0.6.0
228+
once; then add notes per release going forward.
229+
230+
**Effort:** ~30 minutes for the initial backfill; ~5 minutes per release.
231+
232+
**Consumer impact:** Every consumer needs to know what changed. AI agents
233+
specifically benefit — a CHANGELOG is high-signal context per token spent.
234+
235+
### #6 — Better error messages on entity JSON shape mismatches
236+
237+
**Where:** `server/typescript/packages/metadata/src/loader/` (or wherever
238+
the JSON-to-metadata mapper raises validation errors).
239+
240+
**Today:** When a consumer authors `metaobjects/council.json` and a field
241+
key is wrong (e.g. forgets `@` prefix on `@maxLength`, or uses `enum`
242+
instead of `field.enum`), the error is generic ("unknown field key X"). No
243+
line number, no suggestion, no link to the canonical fixture.
244+
245+
The wizardsofodd Phase 2 implementer last session had to mirror conformance
246+
fixtures (`enum-inline/input/meta.enums.json`) because the entity-JSON
247+
shape in my high-level spec didn't match what the loader actually accepted.
248+
Cost a session's worth of iteration.
249+
250+
**Suggested fix:** error messages with (a) the JSON path to the offending
251+
key, (b) a "did you mean ___" suggestion based on Levenshtein distance to
252+
known keys, (c) a link to the most-relevant conformance fixture as a
253+
working example. Even just (a) + (c) would dramatically reduce time-to-fix.
254+
255+
**Effort:** ~4-6 hours. The loader knows where it is in the JSON tree; just
256+
needs to expose that on raised errors.
257+
258+
**Consumer impact:** Onboarding pain reduction. The "first hour with MO"
259+
experience is dominated by entity-shape iteration; making errors actionable
260+
shrinks that to "first 10 minutes."
261+
262+
### #7 — Document camelCase vs snake_case mapping as a deliberate design choice
263+
264+
**Where:** `README.md` + a "conventions" section in the codegen docs.
265+
266+
**Today:** Generated Drizzle uses camelCase TS properties (`councilId`,
267+
`createdAt`) for snake_case SQL columns (`council_id`, `created_at`). This
268+
bit our queries.ts implementation multiple times — every time someone
269+
writes a query they have to remember "TS=camel, SQL=snake." The mapping is
270+
sensible (idiomatic both ways) but the discovery is by-error.
271+
272+
**Suggested fix:** 1-paragraph docs section: "MO codegen maps `snake_case`
273+
metadata field names to `camelCase` TS property names by default, with the
274+
underlying SQL column staying `snake_case`. Override per-field with
275+
`@dbColumn`." Plus a worked example.
276+
277+
**Effort:** ~15 minutes.
278+
279+
**Consumer impact:** Prevents the "wait, why does Drizzle complain about
280+
`councilId` not existing when I wrote `council_id`?" stumble every consumer
281+
hits at least once.
282+
283+
---
284+
285+
## Tier 3 — Nice-to-have
286+
287+
| # | What | Why it's lower priority |
288+
|---|---|---|
289+
| 8 | `metaobjects/` directory name configurable in `metaobjects.config.ts` (currently hard-coded in CLI's `loadMemory`) | Mild irritation; a single rename in the consumer repo gets you there |
290+
| 9 | `meta init` opt-out for `.metaobjects/` agent-docs scaffolding (~50 KB of AGENTS.md/CLAUDE.md) | Easy to delete after the fact if you don't want them |
291+
| 10 | `template.output` codegen for LLM output schemas | Real value for consumers doing structured-output LLMs, but bigger scope; current `generateRenderHandle()` handles both prompt and output identically — meaningful divergence requires its own design |
292+
| 11 | Wider `@metaobjectsdev/react` component coverage (entity detail / list / filter UI in addition to `useEntityForm` + `<CurrencyInput>`) | Bigger product decision; wizardsofodd v1 React work doesn't need it |
293+
294+
---
295+
296+
## Tier 4 — Verification (might already be done; ~10 min spot-checks)
297+
298+
### #12`meta migrate --dialect d1` end-to-end
299+
300+
The `writeMigrationD1()` function exists at
301+
`server/typescript/packages/migrate-ts/src/write-migration-d1.ts`, but does
302+
the CLI flag (`--dialect d1`) actually wire through? Do the output
303+
filenames match wrangler's `NNNN_name.sql` convention, or does the consumer
304+
have to rename?
305+
306+
The existing `.claude/worktrees/meta-migrate-d1-dialect/` worktree suggests
307+
you're already on this — just confirm wizardsofodd can use it cleanly
308+
without filename gymnastics. If a flat `NNNN_init.sql` lands in
309+
`migrations/` and `wrangler d1 migrations apply` picks it up, we're good.
310+
311+
### #13`deleteXById` D1 response-shape bug fix status
312+
313+
In 0.5.0, the generated `deleteCouncilById(id)` helper assumed `result.rowsAffected`
314+
which is a libsql/Turso field — D1 doesn't return it. Helper returned
315+
`false` even on a successful delete. Was this fixed in 0.6.0?
316+
317+
Not on the wizardsofodd hot path (councils are immutable), but worth knowing.
318+
319+
---
320+
321+
## Suggested ordering if you have appetite for Tier 1
322+
323+
If you tackle Tier 1 in order, each item compounds the value of the next:
324+
325+
1. **#3 (add `expected/` to conformance fixture)** — 20 minutes. Immediately
326+
makes the next two changes easier to test/document.
327+
2. **#1 (auto-wire `promptRender()` generator)** — 30 minutes after #3 ships.
328+
Lets consumers drop the custom-generator wiring.
329+
3. **#2 (parameterize `db` in generated queries)** — 1–2 hours after #1.
330+
Biggest "every-consumer-pays-this-tax-forever" win.
331+
332+
Tier 2 items can be batched whenever (#4, #5, #7 are all small docs adds).
333+
334+
---
335+
336+
## What wizardsofodd does in the meantime
337+
338+
This proposal is **not blocking** for wizardsofodd Phase A — every item has
339+
a documented workaround in our plan
340+
(`wizardsofodd.com/docs/superpowers/plans/2026-05-25-metaobjects-0.6-mega.md`).
341+
We'll ship Phase A with the current 0.6.0 (custom-generator wiring, stub
342+
`db.ts`, etc.) and incorporate any upstream improvements when they land
343+
during Phases B/C/D.
344+
345+
If Tier 1 ships before Phase B, we delete ~50 LOC of workaround code and
346+
import from `@metaobjectsdev/codegen-ts/generators` instead.

0 commit comments

Comments
 (0)