@aotter/mantle 0.0.11-alpha.63 → 0.0.11-alpha.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -12
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +181 -0
- package/dist/generate.js.map +1 -0
- package/dist/skills.d.ts +2 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +80 -0
- package/dist/skills.js.map +1 -0
- package/dist/update.d.ts +2 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +387 -0
- package/dist/update.js.map +1 -0
- package/docs/adr/0001-four-atom-manifest-model.md +6 -7
- package/docs/adr/0007-ai-as-primary-author.md +100 -138
- package/docs/adr/0008-structured-diagnostic-shape.md +79 -99
- package/docs/adr/0009-consumer-supplied-manifests.md +101 -228
- package/docs/adr/0012-views-as-public-rest.md +43 -15
- package/docs/adr/0014-auth-better-auth-and-multi-tenant-mcp.md +43 -17
- package/docs/adr/0018-core-starters-repository-boundary.md +155 -0
- package/docs/adr/README.md +8 -6
- package/docs/cloudflare-low-level-composition.md +94 -0
- package/docs/design-atoms.md +59 -57
- package/docs/design-references/editorial-blog-2026-05-05.md +7 -7
- package/docs/labels.md +1 -1
- package/docs/media-uploads.md +1 -1
- package/docs/release-process.md +156 -523
- package/package.json +9 -6
- package/skills/README.md +20 -16
- package/skills/develop/SKILL.md +4 -4
- package/skills/install/SKILL.md +16 -2
- package/skills/plugin/SKILL.md +1 -1
- package/skills/provision/SKILL.md +1 -1
- package/skills/theme/SKILL.md +12 -10
- package/skills/update/SKILL.md +31 -19
- package/skills/customize-design/SKILL.md +0 -215
- package/skills/extend/SKILL.md +0 -257
package/skills/extend/SKILL.md
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: extend
|
|
3
|
-
description: Add new functionality to an existing mantle project — a new Schema, View, Procedure, or Trigger; or wire a feature like a contact form, newsletter signup, comment thread, or filtered list page. Use when the user already has a mantle project and wants to grow it.
|
|
4
|
-
metadata:
|
|
5
|
-
source: "@aotter/mantle"
|
|
6
|
-
sourcePath: skills/extend/SKILL.md
|
|
7
|
-
applies_to: mantle@v0.1.0
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Extend a mantle project
|
|
11
|
-
|
|
12
|
-
The 4-atom manifest model:
|
|
13
|
-
|
|
14
|
-
- **Schema** — the entity (table)
|
|
15
|
-
- **View** — the read API
|
|
16
|
-
- **Procedure** — the typed callable
|
|
17
|
-
- **Trigger** — the event binding (HTTP / lifecycle / cron)
|
|
18
|
-
|
|
19
|
-
Closed enums (`x-mantle-bind` values, `ctx.*` predicates, `Trigger.source.kind`, `Procedure.handler.kind`) are checked by `pnpm validate` — diagnostics return `code` + `suggestion`. If grammar is unclear, run `pnpm introspect` against the current project to see what the manifest compiler accepts, or read the shipped full grammar reference at `node_modules/@aotter/mantle/docs/design-atoms.md`.
|
|
20
|
-
|
|
21
|
-
## Match the user's request to atoms
|
|
22
|
-
|
|
23
|
-
| User says | Add |
|
|
24
|
-
| ------------------------------------------ | -------------------------------------------------------------------- |
|
|
25
|
-
| "I want to publish blog posts" | Schema (`posts`) + per-locale child Schema (translates) + 2 templates |
|
|
26
|
-
| "I want a contact form" | Schema (write target) + Procedure (handler.kind: builtin op:create) + Trigger (http POST /api/contact) |
|
|
27
|
-
| "I want CAPTCHA / Slack notify on submit" | + Procedure (handler.kind: ref) + Trigger (lifecycle before_/after_create) |
|
|
28
|
-
| "I want a /search page filtered by tag" | View with params: { tag } |
|
|
29
|
-
| "I want a public prompt-generator / calculator / configurator page" | A consumer-side `app.get(...)` route in `src/index.ts` — see § Custom public routes |
|
|
30
|
-
| "I want an API key / personal token / paid API / scoped MCP tool" | Procedure/View `requires.auth` plus optional `guard.procedure`; site-owned resolver/handler — see § API and MCP authorization |
|
|
31
|
-
| "I want a /docs/<slug>/edit-history page" | Defer — v0.1 ships `simple` lifecycle only; `editorial` is v0.1.x |
|
|
32
|
-
| "I want comments" | v0.1: anonymous-with-email pattern (Schema + write Procedure). End-user member system is v0.2. |
|
|
33
|
-
|
|
34
|
-
If the user wants something not in the table, ask before guessing.
|
|
35
|
-
|
|
36
|
-
## Step-by-step (the canonical loop)
|
|
37
|
-
|
|
38
|
-
### 1. Write the manifest YAML
|
|
39
|
-
|
|
40
|
-
Schemas / Views / Procedures / Triggers live under `manifests/`. One feature per file is fine; multi-doc YAML (`---` separators) for related atoms is also fine.
|
|
41
|
-
|
|
42
|
-
Example for "newsletter signup":
|
|
43
|
-
|
|
44
|
-
```yaml
|
|
45
|
-
# manifests/newsletter.yaml
|
|
46
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
47
|
-
kind: Schema
|
|
48
|
-
metadata: { name: newsletter-signups }
|
|
49
|
-
spec:
|
|
50
|
-
title: Newsletter signups
|
|
51
|
-
schema:
|
|
52
|
-
type: object
|
|
53
|
-
required: [email]
|
|
54
|
-
properties:
|
|
55
|
-
email: { type: string, format: email }
|
|
56
|
-
createdAt: { type: number, x-mantle-bind: now }
|
|
57
|
-
uniqueIndexes: [[email]]
|
|
58
|
-
lifecycle: simple
|
|
59
|
-
---
|
|
60
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
61
|
-
kind: Procedure
|
|
62
|
-
metadata: { name: subscribe }
|
|
63
|
-
spec:
|
|
64
|
-
input:
|
|
65
|
-
type: object
|
|
66
|
-
required: [email]
|
|
67
|
-
properties:
|
|
68
|
-
email: { type: string, format: email }
|
|
69
|
-
output: { type: object }
|
|
70
|
-
handler:
|
|
71
|
-
kind: builtin
|
|
72
|
-
op: create
|
|
73
|
-
schema: newsletter-signups
|
|
74
|
-
---
|
|
75
|
-
apiVersion: cms.mantle.aotter.net/v1
|
|
76
|
-
kind: Trigger
|
|
77
|
-
metadata: { name: subscribe-http }
|
|
78
|
-
spec:
|
|
79
|
-
source: { kind: http, method: POST, path: /api/subscribe }
|
|
80
|
-
target: { procedure: subscribe }
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 2. Validate immediately
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
pnpm validate
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Common diagnostics:
|
|
90
|
-
|
|
91
|
-
- `DRAFT_KEY_USED` — you wrote a v0.2+ key (e.g. `Trigger.source.kind: cron`); remove it.
|
|
92
|
-
- `LIFECYCLE_NOT_IN_V010` — you wrote `Schema.spec.lifecycle: editorial`; v0.1.0 only ships `simple`.
|
|
93
|
-
- `VIEW_FILTER_PARAM_REF_NOT_REQUIRED` — you reference `{ $param: x }` but `x` is optional; add to `params.required`.
|
|
94
|
-
- `VIEW_PARAMS_RESERVED_NAME` — you declared `params.page` / `.show` / `.cursor`; rename — runtime owns those.
|
|
95
|
-
- `TRIGGER_TARGET_PROCEDURE_UNKNOWN` — typo in `target.procedure`.
|
|
96
|
-
- `HANDLER_NOT_REGISTERED` — Procedure declares `handler.kind: ref` but no `registerHandler('<ref>', fn)` call exists in `src/`.
|
|
97
|
-
|
|
98
|
-
### 3. Wire any handler refs
|
|
99
|
-
|
|
100
|
-
For `Procedure.handler.kind: ref`:
|
|
101
|
-
|
|
102
|
-
```ts
|
|
103
|
-
// src/handlers.ts
|
|
104
|
-
export const handlers = {
|
|
105
|
-
subscribe: async (input) => { /* ... */ return { ok: true }; },
|
|
106
|
-
};
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The CLI greps `src/` for the literal string `'<ref>'` — keep registration somewhere greppable (object literal or `registerHandler('subscribe', fn)` call).
|
|
110
|
-
|
|
111
|
-
### 4. Register a template if the Schema needs HTML output
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
// src/templates/index.ts
|
|
115
|
-
import { newsletterTemplate } from "./newsletter.js";
|
|
116
|
-
registry.registerEntryTemplate("newsletter-signups", newsletterTemplate);
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Skip this if the Schema is internal-only (raw form submissions don't need HTML pages).
|
|
120
|
-
|
|
121
|
-
### 5. Re-emit types + OpenAPI (optional)
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
pnpm emit-types # adds ProcInput_subscribe / ProcOutput_subscribe / Entry_newsletter_signups
|
|
125
|
-
pnpm emit-openapi # adds POST /api/subscribe operation
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Commit both alongside the manifest changes if you keep artifacts under version control.
|
|
129
|
-
|
|
130
|
-
### 6. Try it locally
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
pnpm fixture # only if you added new fixture data
|
|
134
|
-
pnpm dev # restart wrangler
|
|
135
|
-
curl -X POST http://localhost:8787/api/subscribe \
|
|
136
|
-
-H 'content-type: application/json' \
|
|
137
|
-
-d '{"email":"alice@example.com"}'
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Use `?preview=1` on any `/posts/` or `/pages/` URL to render an in-progress draft via the registered template instead of pre-rendered KV HTML.
|
|
141
|
-
|
|
142
|
-
### 7. Run the integration smokes
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
pnpm view-smoke # 10 cases against /api/views/*
|
|
146
|
-
pnpm mcp-smoke # 12 cases against /mcp
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
If you added a new MCP-relevant Schema, per-collection authoring tools auto-emit:
|
|
150
|
-
`create_draft_<segment>` / `update_draft_<segment>` for content lifecycles,
|
|
151
|
-
or `create_record_<segment>` / `update_record_<segment>` for
|
|
152
|
-
`lifecycle: none`. Verify the actual surface with `tools/list`.
|
|
153
|
-
|
|
154
|
-
## API and MCP authorization
|
|
155
|
-
|
|
156
|
-
Read the shipped canonical guide before adding an authenticated public API:
|
|
157
|
-
`node_modules/@aotter/mantle/docs/api-mcp-authorization.md`.
|
|
158
|
-
|
|
159
|
-
Use only the closed grammar:
|
|
160
|
-
|
|
161
|
-
```yaml
|
|
162
|
-
requires:
|
|
163
|
-
auth:
|
|
164
|
-
all:
|
|
165
|
-
- ctx.auth
|
|
166
|
-
- { "ctx.auth.scope": "orders:read" }
|
|
167
|
-
guard:
|
|
168
|
-
procedure: require-active-access
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
- `ctx.auth` means any adapter-verified credential; it is not API-key-only.
|
|
172
|
-
- Repeat `ctx.auth.scope` to require multiple site-owned scopes.
|
|
173
|
-
- Use `ctx.user` as well when a user subject is required.
|
|
174
|
-
- Put current payment, transaction, membership, ownership, or other business
|
|
175
|
-
state in one site-owned `handler.kind: ref` guard Procedure. It receives the
|
|
176
|
-
validated target input/params and the same `HandlerContext`.
|
|
177
|
-
- Put API-key/PAT recognition, hashing, revocation, and normalization in the
|
|
178
|
-
Cloudflare `credentialResolver`. Do not add Core tables, repositories, or a
|
|
179
|
-
generic entitlement layer.
|
|
180
|
-
- Bind the same Procedure to HTTP and MCP Triggers when both transports should
|
|
181
|
-
expose it. Standard remote MCP uses OAuth; it does not promise to send a raw
|
|
182
|
-
REST API key/PAT. Runtime predicates and the guard are shared after caller
|
|
183
|
-
normalization.
|
|
184
|
-
|
|
185
|
-
Expected diagnostics are `UNAUTHENTICATED`/401 for no valid credential,
|
|
186
|
-
`AUTH_DENIED`/403 for a verified caller missing role/scope, and
|
|
187
|
-
`ENTITLEMENT_REQUIRED`/402 when a site guard denies current access. Re-run the
|
|
188
|
-
guide's focused integration command after changing manifests or auth wiring.
|
|
189
|
-
|
|
190
|
-
## Custom public routes (consumer-app freedom)
|
|
191
|
-
|
|
192
|
-
The starter owns its `Hono` app instance. If the user wants a public surface that doesn't fit the 4-atom model — a prompt generator, calculator, configurator, starter directory browser, small interactive widget — add a route directly in `src/index.ts`:
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
// src/index.ts
|
|
196
|
-
import { runtimeRef } from "./bootstrap.js";
|
|
197
|
-
|
|
198
|
-
app.get("/:locale/tools/prompt-generator", async (c) => {
|
|
199
|
-
const runtime = await runtimeRef.get();
|
|
200
|
-
const profiles = await runtime.executeView.execute({
|
|
201
|
-
view: runtime.viewsByName.get("starter_profiles_active")!,
|
|
202
|
-
});
|
|
203
|
-
return c.html(renderPromptGenerator(profiles, c.req.param("locale")));
|
|
204
|
-
});
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
This is consumer-app territory, NOT an SDK feature. The SDK doesn't ship a `customRoutes.ts` declarative API or a type-safe context wrapper — the starter's `Hono` app + `runtime` access via `ref.get()` is enough.
|
|
208
|
-
|
|
209
|
-
SDK mounts (`mountServerEndpoints`, `mountPublicRoutes`, `mountAuthorize`) register their routes early on the Hono `defaultHandler`; consumer `app.get(...)` calls register on the same Hono instance. MCP endpoints are NOT mounted into Hono — they live as `apiHandlers` on the top-level `createOAuthProvider({...})` instance (`/mcp/staff`, `/mcp`), so they share no route table with public Hono routes and can't collide with consumer paths. The `/:locale` param route in `mountPublicRoutes` 404s on unknown locales, so paths under `/tools/...`, `/api/foo`, `/calc`, etc. won't collide as long as your prefix isn't a declared site locale.
|
|
210
|
-
|
|
211
|
-
Right tool when: the user wants ONE public page that doesn't read entries or carry workflow. Wrong tool when: you find yourself reimplementing CRUD, list pagination, or auth gating — those are atom-shaped.
|
|
212
|
-
|
|
213
|
-
Don't fork core templates (post, postList, page, home, contact, notFound) just to add an unrelated public page. Add the route, leave the templates untouched.
|
|
214
|
-
|
|
215
|
-
## Diagnostic recipes
|
|
216
|
-
|
|
217
|
-
| Symptom | Likely cause |
|
|
218
|
-
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
219
|
-
| `INPUT_VALIDATION_FAILED` on a Trigger POST | Body doesn't match `Procedure.spec.input`. Check `pnpm introspect` for the schema. |
|
|
220
|
-
| `AUTH_DENIED` (403) on a contact-form-shaped POST | A `before_create` lifecycle Trigger threw. The diagnostic's `path` names the Procedure. |
|
|
221
|
-
| MCP `tools/list` doesn't show your new collection | Added a Schema but didn't restart wrangler dev. The mount layer caches per-isolate. |
|
|
222
|
-
| New View doesn't appear at `/api/views/<name>` | Same as above — restart. Or: name has uppercase / non-URL-safe chars (use kebab-case). |
|
|
223
|
-
| `VIEW_FILTER_FIELD_NOT_IN_SCHEMA` for a real field | The Schema is referenced via `View.spec.from`; field must be in that Schema's `properties`. |
|
|
224
|
-
|
|
225
|
-
## Live-render dev mode
|
|
226
|
-
|
|
227
|
-
Set `MANTLE_LOCAL_DEV=1` in `.dev.vars` (the starter ships this on by default). The worker bypasses the KV cache for `post` / `postList` / `page` routes and re-renders via the registered templates against current D1 state on every request. Edit `Header.tsx` / `Layout.tsx` / `styles.ts` / `i18n/*.json` and reload — every page reflects the change immediately, no `pnpm fixture` rebake.
|
|
228
|
-
|
|
229
|
-
Production / CI: leave `MANTLE_LOCAL_DEV` unset so the publish-pipeline path is exercised.
|
|
230
|
-
|
|
231
|
-
## Stale-KV gotcha (when you change shared chrome)
|
|
232
|
-
|
|
233
|
-
The starter renders **registered templates** (post / postList / page) at publish time and caches the HTML in KV. **Request-time templates** (home / contact / notFound) compose fresh on each request.
|
|
234
|
-
|
|
235
|
-
If you change any module-init resolved chrome — `src/theme.default/components/Layout.tsx`, `PageShell.tsx`, `Header.tsx`, `Footer.tsx`, `styles.ts`, or `src/i18n/*.json` — or fork any of those into `src/theme/` — the new chrome shows on home / contact / notFound immediately, but post / postList / page keep serving the OLD chrome from KV until re-publish. PageShell is on the same module-init slot-resolution path as Header/Footer, so a forked PageShell triggers the same behavior even though the override lives at a different layer.
|
|
236
|
-
|
|
237
|
-
Local dev fix: `pnpm fixture` rebakes everything from seed data.
|
|
238
|
-
|
|
239
|
-
Production fix: iterate every published entry and call `runtime.requestPublish.execute({ id })`. A `mantle republish-all` CLI is on the v0.1.x roadmap; until then, a one-shot script that pulls `runtime.listEntries` for every collection and re-publishes each row is the right pattern.
|
|
240
|
-
|
|
241
|
-
## Don't
|
|
242
|
-
|
|
243
|
-
- Don't add a Schema-level public-read flag (`Schema.spec.expose.rest` etc) — public reads always go through Views.
|
|
244
|
-
- Don't add a non-`$param` filter sentinel (`{ $env: ... }`, `{ $cookie: ... }`, `{ $now }`) — none are in v0.1.
|
|
245
|
-
- Don't bypass the chokepoint by writing to D1 directly — every mutation MUST go through `runtime.entries` (lifecycle hooks fire there).
|
|
246
|
-
- Don't use `Trigger.source.kind: cron / queue` — DRAFT, parser rejects. MCP is
|
|
247
|
-
shipped; declare `source: { kind: mcp, surface: public | staff }` and bind it
|
|
248
|
-
to a declared Procedure.
|
|
249
|
-
- Don't use `Procedure.spec.requires.window` / `.quota` — DRAFT.
|
|
250
|
-
- Don't write a Procedure with `handler.kind: builtin` and `op: archive` on a `lifecycle: simple` Schema — boot rejects (archive is editorial-only).
|
|
251
|
-
- Don't paste secrets into a manifest (`requires.auth.all` carries predicates only). Secrets go in `wrangler secret put`.
|
|
252
|
-
|
|
253
|
-
## When you're done
|
|
254
|
-
|
|
255
|
-
1. Show the user the new endpoint(s) — `curl` example for each.
|
|
256
|
-
2. Show the diff of `openapi.json` if you re-emitted it (number of new operationIds).
|
|
257
|
-
3. If the new feature has a UI dimension (template, post page, list page) — visually verify in the dev server before claiming done. UI changes can't be type-checked.
|