@happyvertical/smrt-core 0.57.34 → 0.57.35
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/agents/recipes.md +3 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/recipe.d.ts +8 -1
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +7 -0
- package/dist/recipe.js.map +1 -1
- package/dist/smrt-knowledge.json +5 -5
- package/package.json +4 -4
package/dist/manifest.json
CHANGED
package/dist/recipe.d.ts
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* }
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
-
import type { RecipeDemoSeed, RecipeExposureNarrowing, RecipeFieldOptions, RecipeProvider, RecipeRuntime, RecipeSurface } from '@happyvertical/smrt-types';
|
|
34
|
+
import type { RecipeDemoSeed, RecipeExposure, RecipeExposureNarrowing, RecipeFieldOptions, RecipeProvider, RecipeRuntime, RecipeSurface } from '@happyvertical/smrt-types';
|
|
35
35
|
/**
|
|
36
36
|
* A model class as a recipe references it. Any constructor qualifies; the
|
|
37
37
|
* parameter list is `never[]` so models with required constructor options
|
|
@@ -138,6 +138,13 @@ export declare abstract class SmrtRecipe {
|
|
|
138
138
|
static surfaces?: readonly RecipeSurface[];
|
|
139
139
|
/** Providers and secrets the recipe needs or can use (#3708). */
|
|
140
140
|
static providers?: readonly RecipeProvider[];
|
|
141
|
+
/**
|
|
142
|
+
* `'ui-only'` declares that every model of the recipe is closed to the
|
|
143
|
+
* generated API, MCP and CLI on purpose, so a host offers the recipe and its
|
|
144
|
+
* screens but tells the user agents cannot read it. The scanner rejects it
|
|
145
|
+
* when any model is exposed. Omit it for every other recipe.
|
|
146
|
+
*/
|
|
147
|
+
static exposure?: RecipeExposure;
|
|
141
148
|
/** Where the recipe's runtime pieces can run; omitted means `both`. */
|
|
142
149
|
static runtime?: RecipeRuntime;
|
|
143
150
|
/** Demo fixture data: a fixture export or small inline JSON (#3708). */
|
package/dist/recipe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recipe.d.ts","sourceRoot":"","sources":["../src/recipe.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"recipe.d.ts","sourceRoot":"","sources":["../src/recipe.ts"],"names":[],"mappings":"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"}
|
package/dist/recipe.js
CHANGED
|
@@ -49,6 +49,13 @@ var SmrtRecipe = class {
|
|
|
49
49
|
static surfaces;
|
|
50
50
|
/** Providers and secrets the recipe needs or can use (#3708). */
|
|
51
51
|
static providers;
|
|
52
|
+
/**
|
|
53
|
+
* `'ui-only'` declares that every model of the recipe is closed to the
|
|
54
|
+
* generated API, MCP and CLI on purpose, so a host offers the recipe and its
|
|
55
|
+
* screens but tells the user agents cannot read it. The scanner rejects it
|
|
56
|
+
* when any model is exposed. Omit it for every other recipe.
|
|
57
|
+
*/
|
|
58
|
+
static exposure;
|
|
52
59
|
/** Where the recipe's runtime pieces can run; omitted means `both`. */
|
|
53
60
|
static runtime;
|
|
54
61
|
/** Demo fixture data: a fixture export or small inline JSON (#3708). */
|
package/dist/recipe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recipe.js","names":[],"sources":["../src/recipe.ts"],"sourcesContent":["/**\n * `SmrtRecipe` — a declared, user-facing unit of app functionality (#3590).\n *\n * A recipe is smaller than a package: \"Sales\" or \"Customers\", not all of\n * smrt-commerce. It names the models that make up the unit, the navigation\n * entries it contributes, the recipes it needs first, and curation hints for\n * how its models' forms present. It is declaration only: not persisted and not\n * `@smrt()`-decorated.\n *\n * Declare a recipe as a subclass with static properties, in the package that\n * owns the models. The scanner reads the statics at build time and emits them\n * as the top-level `recipes` array of `manifest.json` and\n * `smrt-knowledge.json`; nothing here runs at that point, so every static must\n * be spelled literally (model entries are class references).\n *\n * @example\n * ```typescript\n * export class SalesRecipe extends SmrtRecipe {\n * static id = 'commerce.sales';\n * static label = 'Sales';\n * static summary = 'Take customer orders and track them.';\n * static synonyms = ['sales orders', 'orders'];\n * static models = [Order];\n * static nav = [{ label: 'Sales Orders', model: Order }];\n * static requires = ['commerce.customers'];\n * static section = { id: 'sales', label: 'Sales', icon: 'shoppingBag' };\n * static options = {\n * Order: { fields: { status: { default: 'draft', locked: true } } },\n * };\n * static help = './sales.recipe.md';\n * }\n * ```\n */\n\nimport type {\n RecipeDemoSeed,\n RecipeExposureNarrowing,\n RecipeFieldOptions,\n RecipeProvider,\n RecipeRuntime,\n RecipeSurface,\n} from '@happyvertical/smrt-types';\n\n/**\n * A model class as a recipe references it. Any constructor qualifies; the\n * parameter list is `never[]` so models with required constructor options\n * remain assignable.\n */\nexport type SmrtRecipeModel = abstract new (...args: never[]) => object;\n\n/** One navigation entry a recipe contributes. */\nexport interface SmrtRecipeNavEntry {\n label: string;\n model: SmrtRecipeModel;\n /** A shell icon name (`users`, `calendar`...) drawn on the entry's row. */\n icon?: string;\n /** One friendly line for a business owner, shown on the section card. */\n description?: string;\n /**\n * Fixed key making the entry's layout id `item:<pkg>:<Model>:<key>`. Needed\n * for a second entry over the same model, and for a `filter`.\n */\n key?: string;\n /** What \"New\" creates when the label is not a countable noun. */\n noun?: string;\n /**\n * Narrow the entry to rows whose `field` equals `value`; new rows from the\n * view carry the value. Needs a `key`.\n */\n filter?: { field: string; value: string };\n}\n\n/** Recipes sharing a `group.id` share one card in a host. */\nexport interface SmrtRecipeGroup {\n id: string;\n label: string;\n summary?: string;\n}\n\n/** The navigation section a recipe suggests; same `id` shares a section. */\nexport interface SmrtRecipeSection {\n id: string;\n label: string;\n icon?: string;\n description?: string;\n}\n\n/**\n * Curation hints for one model, keyed in `static options` by the model's class\n * name. Hints only ever refine what the model already declares: they name its\n * existing fields and can narrow, never widen, its API/MCP/CLI exposure.\n */\nexport interface SmrtRecipeModelOptions {\n fields?: Record<string, RecipeFieldOptions>;\n exposure?: {\n api?: RecipeExposureNarrowing;\n mcp?: RecipeExposureNarrowing;\n cli?: RecipeExposureNarrowing;\n };\n}\n\nexport abstract class SmrtRecipe {\n /** Stable dotted id, e.g. `commerce.sales`. Unique across all recipes. */\n static id: string;\n /** Display name, e.g. `Sales`. */\n static label: string;\n /** One sentence on what the unit gives the user. */\n static summary: string;\n /** Other words someone might use to ask for it. */\n static synonyms: readonly string[] = [];\n /** Models the unit brings. They must belong to the declaring package. */\n static models: readonly SmrtRecipeModel[];\n /** Navigation entries; each model must also be listed in `models`. */\n static nav: readonly SmrtRecipeNavEntry[] = [];\n /** Ids of recipes needed first; they may live in other packages. */\n static requires: readonly string[] = [];\n /**\n * Alternatives: at least one id of each inner list must be on. Adding the\n * recipe with none on adds the first of the list.\n */\n static requiresAny: readonly (readonly string[])[] = [];\n /**\n * Card the recipe is a sub-switch of in a host such as an app shell. Recipes\n * with the same `group.id` share it; the first declaration supplies the\n * card's `label` and `summary`.\n */\n static group?: SmrtRecipeGroup;\n /**\n * Navigation section the recipe suggests its `nav` entries sit under. The\n * host owns sections and the user may rename them, so keep `id` stable.\n */\n static section?: SmrtRecipeSection;\n /** Curation hints keyed by model class name. */\n static options: Readonly<Record<string, SmrtRecipeModelOptions>> = {};\n /**\n * Optional user-facing help (#3591): a path to a Markdown file beside the\n * recipe, such as `./sales.recipe.md`. The scanner reads it at build time and\n * embeds `{ markdown, fieldRefs }` in the recipe's manifest entry. See\n * `renderHelp` for how a host follows the app's options.\n */\n static help?: string;\n /**\n * Non-model surfaces (#3708): shell widgets, routes, settings panels,\n * playground entries and overview `widget`s (#3727, with an options schema,\n * data needs and allowed overview ids). Components are referenced by\n * `'<module specifier>#<ExportName>'`, never imported by the manifest.\n * Write the array `as const` so literal kinds and slots survive.\n */\n static surfaces?: readonly RecipeSurface[];\n /** Providers and secrets the recipe needs or can use (#3708). */\n static providers?: readonly RecipeProvider[];\n /** Where the recipe's runtime pieces can run; omitted means `both`. */\n static runtime?: RecipeRuntime;\n /** Demo fixture data: a fixture export or small inline JSON (#3708). */\n static demoSeed?: RecipeDemoSeed;\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"recipe.js","names":[],"sources":["../src/recipe.ts"],"sourcesContent":["/**\n * `SmrtRecipe` — a declared, user-facing unit of app functionality (#3590).\n *\n * A recipe is smaller than a package: \"Sales\" or \"Customers\", not all of\n * smrt-commerce. It names the models that make up the unit, the navigation\n * entries it contributes, the recipes it needs first, and curation hints for\n * how its models' forms present. It is declaration only: not persisted and not\n * `@smrt()`-decorated.\n *\n * Declare a recipe as a subclass with static properties, in the package that\n * owns the models. The scanner reads the statics at build time and emits them\n * as the top-level `recipes` array of `manifest.json` and\n * `smrt-knowledge.json`; nothing here runs at that point, so every static must\n * be spelled literally (model entries are class references).\n *\n * @example\n * ```typescript\n * export class SalesRecipe extends SmrtRecipe {\n * static id = 'commerce.sales';\n * static label = 'Sales';\n * static summary = 'Take customer orders and track them.';\n * static synonyms = ['sales orders', 'orders'];\n * static models = [Order];\n * static nav = [{ label: 'Sales Orders', model: Order }];\n * static requires = ['commerce.customers'];\n * static section = { id: 'sales', label: 'Sales', icon: 'shoppingBag' };\n * static options = {\n * Order: { fields: { status: { default: 'draft', locked: true } } },\n * };\n * static help = './sales.recipe.md';\n * }\n * ```\n */\n\nimport type {\n RecipeDemoSeed,\n RecipeExposure,\n RecipeExposureNarrowing,\n RecipeFieldOptions,\n RecipeProvider,\n RecipeRuntime,\n RecipeSurface,\n} from '@happyvertical/smrt-types';\n\n/**\n * A model class as a recipe references it. Any constructor qualifies; the\n * parameter list is `never[]` so models with required constructor options\n * remain assignable.\n */\nexport type SmrtRecipeModel = abstract new (...args: never[]) => object;\n\n/** One navigation entry a recipe contributes. */\nexport interface SmrtRecipeNavEntry {\n label: string;\n model: SmrtRecipeModel;\n /** A shell icon name (`users`, `calendar`...) drawn on the entry's row. */\n icon?: string;\n /** One friendly line for a business owner, shown on the section card. */\n description?: string;\n /**\n * Fixed key making the entry's layout id `item:<pkg>:<Model>:<key>`. Needed\n * for a second entry over the same model, and for a `filter`.\n */\n key?: string;\n /** What \"New\" creates when the label is not a countable noun. */\n noun?: string;\n /**\n * Narrow the entry to rows whose `field` equals `value`; new rows from the\n * view carry the value. Needs a `key`.\n */\n filter?: { field: string; value: string };\n}\n\n/** Recipes sharing a `group.id` share one card in a host. */\nexport interface SmrtRecipeGroup {\n id: string;\n label: string;\n summary?: string;\n}\n\n/** The navigation section a recipe suggests; same `id` shares a section. */\nexport interface SmrtRecipeSection {\n id: string;\n label: string;\n icon?: string;\n description?: string;\n}\n\n/**\n * Curation hints for one model, keyed in `static options` by the model's class\n * name. Hints only ever refine what the model already declares: they name its\n * existing fields and can narrow, never widen, its API/MCP/CLI exposure.\n */\nexport interface SmrtRecipeModelOptions {\n fields?: Record<string, RecipeFieldOptions>;\n exposure?: {\n api?: RecipeExposureNarrowing;\n mcp?: RecipeExposureNarrowing;\n cli?: RecipeExposureNarrowing;\n };\n}\n\nexport abstract class SmrtRecipe {\n /** Stable dotted id, e.g. `commerce.sales`. Unique across all recipes. */\n static id: string;\n /** Display name, e.g. `Sales`. */\n static label: string;\n /** One sentence on what the unit gives the user. */\n static summary: string;\n /** Other words someone might use to ask for it. */\n static synonyms: readonly string[] = [];\n /** Models the unit brings. They must belong to the declaring package. */\n static models: readonly SmrtRecipeModel[];\n /** Navigation entries; each model must also be listed in `models`. */\n static nav: readonly SmrtRecipeNavEntry[] = [];\n /** Ids of recipes needed first; they may live in other packages. */\n static requires: readonly string[] = [];\n /**\n * Alternatives: at least one id of each inner list must be on. Adding the\n * recipe with none on adds the first of the list.\n */\n static requiresAny: readonly (readonly string[])[] = [];\n /**\n * Card the recipe is a sub-switch of in a host such as an app shell. Recipes\n * with the same `group.id` share it; the first declaration supplies the\n * card's `label` and `summary`.\n */\n static group?: SmrtRecipeGroup;\n /**\n * Navigation section the recipe suggests its `nav` entries sit under. The\n * host owns sections and the user may rename them, so keep `id` stable.\n */\n static section?: SmrtRecipeSection;\n /** Curation hints keyed by model class name. */\n static options: Readonly<Record<string, SmrtRecipeModelOptions>> = {};\n /**\n * Optional user-facing help (#3591): a path to a Markdown file beside the\n * recipe, such as `./sales.recipe.md`. The scanner reads it at build time and\n * embeds `{ markdown, fieldRefs }` in the recipe's manifest entry. See\n * `renderHelp` for how a host follows the app's options.\n */\n static help?: string;\n /**\n * Non-model surfaces (#3708): shell widgets, routes, settings panels,\n * playground entries and overview `widget`s (#3727, with an options schema,\n * data needs and allowed overview ids). Components are referenced by\n * `'<module specifier>#<ExportName>'`, never imported by the manifest.\n * Write the array `as const` so literal kinds and slots survive.\n */\n static surfaces?: readonly RecipeSurface[];\n /** Providers and secrets the recipe needs or can use (#3708). */\n static providers?: readonly RecipeProvider[];\n /**\n * `'ui-only'` declares that every model of the recipe is closed to the\n * generated API, MCP and CLI on purpose, so a host offers the recipe and its\n * screens but tells the user agents cannot read it. The scanner rejects it\n * when any model is exposed. Omit it for every other recipe.\n */\n static exposure?: RecipeExposure;\n /** Where the recipe's runtime pieces can run; omitted means `both`. */\n static runtime?: RecipeRuntime;\n /** Demo fixture data: a fixture export or small inline JSON (#3708). */\n static demoSeed?: RecipeDemoSeed;\n}\n"],"mappings":";AAsGA,IAAsB,aAAtB,MAAiC;;CAE/B,OAAO;;CAEP,OAAO;;CAEP,OAAO;;CAEP,OAAO,WAA8B,CAAC;;CAEtC,OAAO;;CAEP,OAAO,MAAqC,CAAC;;CAE7C,OAAO,WAA8B,CAAC;;;;;CAKtC,OAAO,cAA8C,CAAC;;;;;;CAMtD,OAAO;;;;;CAKP,OAAO;;CAEP,OAAO,UAA4D,CAAC;;;;;;;CAOpE,OAAO;;;;;;;;CAQP,OAAO;;CAEP,OAAO;;;;;;;CAOP,OAAO;;CAEP,OAAO;;CAEP,OAAO;AACT"}
|
package/dist/smrt-knowledge.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"sensitiveFieldsExcluded": true,
|
|
4
4
|
"generatedAt": "1970-01-01T00:00:00.000Z",
|
|
5
5
|
"packageName": "@happyvertical/smrt-core",
|
|
6
|
-
"packageVersion": "0.57.
|
|
6
|
+
"packageVersion": "0.57.35",
|
|
7
7
|
"sourceManifestPath": "dist/manifest.json",
|
|
8
8
|
"agentDocPath": "AGENTS.md",
|
|
9
9
|
"sourceHashes": {
|
|
10
|
-
"manifest": "
|
|
11
|
-
"packageJson": "
|
|
10
|
+
"manifest": "f1968fbedee90b4b9d02f5b77f92daac9629bff2b2f86ea212fe41b726db1f91",
|
|
11
|
+
"packageJson": "c900d414d28ccabab28323a1f56b27e3d57967686a38af899f728ad435d727f3",
|
|
12
12
|
"agents": "952dd7df7461bd7657b346f3ea63f1d240a625863b6ceef7b0f6d4960f978068",
|
|
13
13
|
"moduleDoc:agents/object-runtime.md": "fd2748649e820c2fe5960fa4a44c5f3e2a5afa57da89925e8b4cb3b53d67a07b",
|
|
14
14
|
"moduleDoc:agents/revision-guard.md": "f6ea32a6860964c98aba14b93587f40b33c8f7d0deccb2c54d21bff45db3da42",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"moduleDoc:agents/registry-snapshot.md": "6234aa540427396d1dda1825a4641b1072e247187d5ddf413ae46c691344ee30",
|
|
27
27
|
"moduleDoc:agents/model-moves.md": "c64147fee1b7ec242fd1c178e7aea7cc90cafc704dee2738141c7a6d61403cd8",
|
|
28
28
|
"moduleDoc:agents/run-once.md": "43c589a3cab2ff61147128014cc0084509faf623eb76860140d589539420a1f3",
|
|
29
|
-
"moduleDoc:agents/recipes.md": "
|
|
29
|
+
"moduleDoc:agents/recipes.md": "583025a8ae6e4ab8a0b88181097027ac895729ed8d631903facec32e115bb63a",
|
|
30
30
|
"moduleDoc:agents/cookbook.md": "20c256f4f6219f9b2add6365ef930e0bb3eda37e7866ddbc4bdd466830f76e4f",
|
|
31
31
|
"moduleDoc:agents/cookbook-engine.md": "f8e6869021b6cc214fe34da20b73a83509abc10bab71299ea9668b47a1c4307d",
|
|
32
32
|
"moduleDoc:agents/browser-entry.md": "8d928babfe7308512c901a12c23fbfb7ff395f6b1cddce5cd069e4ece38743da",
|
|
@@ -1233,7 +1233,7 @@
|
|
|
1233
1233
|
{
|
|
1234
1234
|
"path": "agents/recipes.md",
|
|
1235
1235
|
"module": "recipes",
|
|
1236
|
-
"content": "# SmrtRecipe: declared units of app functionality (#3590)\n\nA **recipe** is a small, named, user-facing unit (\"Sales\", \"Customers\"),\nsmaller than a package. An app or agent adds a recipe and gets that section:\nnavigation, list and create/edit views, and its prerequisites, without every\nmodel in the owning package. Source: `src/recipe.ts` (authoring base class),\n`packages/scanner/src/recipes.ts` (matcher and validation), types in\n`@happyvertical/smrt-types` (`RecipeDefinition`).\n\n## Authoring\n\n`SmrtRecipe` is an abstract, **non-persisted, non-`@smrt()`** base class\nexported from both core entries (`index.ts` and `browser.ts`). A recipe is a\nsubclass with static properties, in the package that owns its models:\n\n```ts\nexport class SalesRecipe extends SmrtRecipe {\n static id = 'commerce.sales'; // dotted lowercase, unique\n static label = 'Sales';\n static summary = 'Take customer orders and track them.';\n static synonyms = ['sales orders', 'orders'];\n static models = [Order]; // class references\n static nav = [{ label: 'Sales Orders', model: Order }];\n static requires = ['commerce.customers']; // recipe ids, any package\n static options = { Order: { fields: { status: { default: 'draft' } } } };\n}\n```\n\nStatics are read **structurally** (nothing is evaluated), so each must be\nspelled literally; `models` and `nav[].model` take identifiers. Write\n`static options = { ... } as const` so `visibility` stays a literal type.\nThe emitted `options` are keyed by qualified model name.\n\n## Host metadata: group, section, nav detail, alternatives (#3604)\n\nEverything a catalog host (an app shell, an agent, a CLI) needs\nto present a recipe lives on the recipe, so it reads the manifest or knowledge\nartifact instead of importing runtime code. All are optional and additive:\n\n| Static | Shape | Meaning |\n| --- | --- | --- |\n| `group` | `{ id, label, summary? }` | Recipes with the same `group.id` share one card; the first declaration of an id supplies `label` and `summary`. Declaration order is the sub-switch order. |\n| `section` | `{ id, label, icon?, description? }` | Navigation section the recipe suggests for its `nav` entries. The host owns sections and the user may rename them, so keep `id` stable; the same `id` shares a section. |\n| `requiresAny` | `string[][]` | At least one id of each inner list must be on (`[['products.simple', 'products.clothing']]`); adding the recipe with none on adds the first. |\n| `nav[].icon` / `description` | string | Shell icon name and one friendly line for a business owner. The icon must be a key of `SHELL_ICON_PATHS` (smrt-svelte `admin-shell/shell-icons.ts`, camelCase such as `fileText` or `shoppingBag`); the same holds for `section.icon`. `packages/scanner/src/__tests__/workspace-recipes.test.ts` fails on any other name. |\n| `nav[].noun` | string | What \"New\" creates when the label is not countable (\"Stock levels\" -> `stock entry`). |\n| `nav[].key` | slug | Fixed key for the layout id `item:<pkg>:<Model>:<key>`. Required for a second entry over the same model and for a `filter`. |\n| `nav[].filter` | `{ field, value }` | Narrows the entry to rows where `field` equals `value` (Ingredients over Products: `productType` = `material`). Needs a `key`. |\n| `options.<Model>.fields.<f>.required` | boolean | The form refuses to save without a value. |\n\nAcross recipes of one package the scanner also rejects a repeated nav `key`\nover the same model (it names one layout id) and one `group`/`section` id with\ntwo different labels. `packages/scanner/src/__tests__/workspace-recipes.test.ts`\nscans every recipe in the workspace and checks ids, `requires`/`requiresAny`\ntargets (which a single package cannot see) and shared labels.\n\nChild models (line items) are simply listed in `models` without a `nav` entry.\nValidation: `group`/`section` take only their keys, slug ids, non-empty\nstrings; `requiresAny` ids follow the recipe-id format and cannot name the\nrecipe itself; each (model, key) pair appears once in `nav`; a `filter.field`\nmust be a field the model declares (checked in core on the merged manifest,\n`assertRecipeOptions`).\n\nNot part of the recipe declaration (still host-local): a host's\ncross-package `forms` and `extends` (form records spanning profiles, variants\nand SKUs).\n\nUser-facing field text for the recipes comes from `@field({ description })` on\nthe models (the glossary seed), so a package that declares a recipe carries the\ndescriptions of every field the recipe shows. A field the package does not\ndeclare itself (`parentId` from `SmrtHierarchical`) is described by the recipe's\n`options.<Model>.fields.<f>.help`, which wins over the description in the\nglossary. Write all of it in plain words: no API, JSON, REST, MCP, SQL or UUID\noutside help that is about connecting other tools.\n\n## Non-model surfaces, providers, runtime, demo seed (#3708)\n\nMany features are not a list of models: a shell widget (assistant dock,\nnotification bell), a component, a route, a settings panel. All four statics are\noptional and additive; a recipe that declares none emits none of the keys. They\nare emitted on the recipe entry in `manifest.json` and `smrt-knowledge.json`.\nComponents are named by `'<package specifier>#<ExportName>'` and never imported\nat scan time; a host resolves them. Relative or absolute specifiers are rejected.\n\n| Static | Shape | Meaning |\n| --- | --- | --- |\n| `surfaces` | `RecipeSurface[]` | `{ kind: 'shell-widget', slot, export, label, icon? }`, `{ kind: 'route', path, export, label }`, `{ kind: 'settings-panel', export, label }`, `{ kind: 'playground', export, label? }`, `{ kind: 'widget', type, export, label, ... }` (see below). `slot` is a smrt-svelte `ShellSlot`; `path` starts with `/` and has no whitespace, `?`, `#` or `..`. |\n| `providers` | `RecipeProvider[]` | `{ id, kind, options, required, secrets?, browserOptions? }`: lowercase slugs, `options` non-empty and distinct, `required` always written, `secrets` distinct `UPPER_SNAKE` names (never values), `browserOptions` a non-empty subset of `options` that run in a browser with none of the secrets (#3709). Ids are unique per recipe. |\n| `runtime` | `'browser' \\| 'server' \\| 'both'` | Where the recipe's runtime pieces can run. Omitted means `both`; the value is emitted as authored. The build refines it into `demo` (below). |\n| `demoSeed` | `{ export } \\| { data }` | A fixture export reference, or inline JSON of at most 8 KB, for demo hosts. Exactly one key. |\n\nWrite `surfaces` with `as const` (like `options`) so `kind` and `slot` stay\nliteral types. The slot union is `RecipeShellSlot` in smrt-types (types only) and the\nscanner's `RECIPE_SHELL_SLOTS` list copies smrt-svelte's `SHELL_SLOTS`; a\ntype-level test in smrt-svelte and a scanner test keep them equal, so there is\nno runtime dependency on smrt-svelte. Inline `demoSeed.data` must be finite JSON. Every violation (unknown\nkind or slot, extra key, duplicate route path or provider id, malformed\nreference) is a scan error. Example: `events.calendar` in\n`packages/events/src/recipes.ts`.\n\n## Widget surfaces (#3727)\n\n`{ kind: 'widget', ... }` contributes a widget to customizable overviews\n(smrt-svelte `./overview`). The manifest carries everything a host needs to\nregister it; the helper `registerRecipeWidgets` in smrt-svelte does that with an\ninjected module resolver, so nothing is imported at scan time.\n\n```ts\nstatic surfaces = [\n {\n kind: 'widget',\n type: 'sales-total', // registry type, unique per package\n export: '@acme/shop/svelte#SalesTotal', // the component\n label: 'Sales total', // text or an i18n key\n description: 'Order totals for the period.',\n icon: 'chart',\n version: 2, // option-schema version (default 1)\n migrate: '@acme/shop/server#migrateSalesTotal', // needs version above 1\n optionsEditor: '@acme/shop/svelte#SalesOptions', // optional dependent-choice editor\n options: [\n { key: 'model', type: 'model', label: 'Model', required: true },\n { key: 'style', type: 'enum', label: 'Style', default: 'bar',\n choices: [{ value: 'bar', label: 'Bar' }, { value: 'line', label: 'Line' }] },\n ],\n data: { load: '@acme/shop/server#loadSalesTotal', models: ['@acme/shop:Order'] },\n allowedIn: ['shop.home'], // overview ids; omit for any\n sectionDefault: true, // optional: this widget IS the section page\n defaultSpan: 2, minSpan: 1, maxSpan: 3,\n },\n] as const;\n```\n\nEmitted keys, in this order: `kind`, `type`, `export`, `label`, `description`,\n`icon`, `version`, `migrate`, `optionsEditor`, `options`, `data`, `allowedIn`,\n`sectionDefault`, `defaultSpan`, `minSpan`, `maxSpan`; absent ones are omitted. Rules (each violation is a scan\nerror, same strictness as the other kinds):\n\n- `type` is a lowercase kebab id (`[a-z][a-z0-9-]{0,31}`), unique among a\n recipe's surfaces and across the package's recipes. `export`, `data.load`,\n `migrate`, and `optionsEditor` are `'<package specifier>#<ExportName>'`;\n relative specifiers are rejected. `label` is required. A UI host resolves an\n `optionsEditor` lazily for dependent or remote choices; the server registry\n never imports it, and every saved value still passes the ordinary option schema.\n- `options` is a non-empty list of plain-JSON fields limited to the overview\n option types: `text`, `markdown`, `identifier`, `model`, `integer`, `number`,\n `boolean`, closed `enum` (non-empty distinct `choices`). There is no free-form\n query, expression, URL or SQL type. Keys are camelCase (`[A-Za-z][A-Za-z0-9]{0,31}`)\n and unique; `default` must pass its field's own rule; `min`/`max` only on\n numbers, `maxLength` only on text. Only `key`, `type`, `label`, `help`,\n `required`, `default`, `min`, `max`, `maxLength`, `choices` are accepted.\n- `sectionDefault: true` makes the widget the section page of the recipe's\n section: when the recipe leads a navigation section (the first recipe\n suggesting it), a host rendering data widgets defaults that section's\n overview to this widget alone, full width, instead of the generic shortcuts,\n count and latest records (smrt-svelte `sectionDefaultWidget`). At most one per\n recipe, and every required option needs a `default`, since it is placed with\n no options. The person can still edit the page.\n- `data` takes `load` and/or `models` (qualified class names, distinct). The\n scanner cannot see other packages, so a model is checked for format only.\n- `allowedIn` is a non-empty list of distinct overview ids\n (`[A-Za-z0-9][A-Za-z0-9_.:-]{0,95}`); spans are integers 1 to 4 with\n `minSpan <= defaultSpan <= maxSpan`.\n\nThe scanner's copies of these rules live in `packages/scanner/src/recipe-widgets.ts`\nand a scanner test compares them to the smrt-svelte source. See\n[overview-surfaces.md](../../smrt-svelte/agents/overview-surfaces.md#recipe-widgets-recipe-widgetsts)\nfor how a host registers them.\n\n## Browser-demo mode: Live, Mock, Sample, Server (#3709)\n\nA catalog host (smrt-planner) labels each feature by how far it runs in a\nbrowser-only demo. The label is **derived at build time**, never authored: the\nmanifest build combines what the bundle-gate measured for the recipe's package\nwith the recipe's own `runtime`, `providers` and `demoSeed`, and emits it as\n`demo` on the recipe entry and `browser` on the package. A recipe that declares\nnone of those keeps its shape except for `demo`, which every recipe of a\nmeasured package gets.\n\n```jsonc\n// manifest.json and smrt-knowledge.json, top level\n\"browser\": {\n \"status\": \"browser-safe\" | \"server-only\",\n \"issues\": [\"#3624\"], // tracking issues; omitted when browser-safe\n \"reason\": \"node:crypto\", // the package's own blocker; omitted when only inherited\n \"via\": [\"@happyvertical/smrt-tenancy\"] // inherited from these; omitted when none\n}\n// on each entry of \"recipes\"\n\"demo\": {\n \"mode\": \"live\" | \"mock\" | \"sample\" | \"server\",\n \"reasons\": [\"Needs a server because ...\"], // plain sentences, in rule order\n \"mocked\": [\"smtp\"] // providers a demo fakes; omitted when none\n}\n```\n\n| Mode | Meaning |\n| --- | --- |\n| `live` | Real browser data layer (PGlite); nothing faked. |\n| `mock` | Runs in the browser, but a provider (sending mail, OAuth) is faked. |\n| `sample` | Runs in the browser on `demoSeed` fixtures only. |\n| `server` | Needs a server; a browser-only host cannot offer it. |\n\n**Package capability.** The browser gate (`packages/bundle-gate`) fails any\nmodel package that is not in its expected-failures list and does not build for\na browser, and any listed package that now builds. So a package is `server-only`\nexactly when it is listed. The ratchet attributes a defect to the package that\nowns the fix, so a dependent passes while still dragging the failure into a\nbrowser bundle; the derivation therefore also marks a package `server-only`\nwhen a workspace package it depends on (transitively, through `dependencies`)\nis, and records them in `via`. `smrt-core` is the platform baseline and does not\npropagate. The table lives in `src/manifest/browser-capability.generated.ts`\n(generated, never edited) and a bundle-gate spec fails when it is stale; see\nthat package's AGENTS.md for the regenerate command. A package the gate does\nnot measure (an app's own, a UI package) emits no `browser`, and its recipes get\na `demo` only when they declare `runtime`, `providers` or `demoSeed`.\n\n**Rules** (`src/recipe-demo.ts`, pure and browser-safe; the worst mode wins,\n`live` < `mock` < `sample` < `server`):\n\n1. `runtime: 'server'` is `server`. `runtime: 'both'` or omitted makes no claim:\n the package capability refines it, so `both` on a `server-only` package is\n `server`.\n2. A `server-only` package is `server`.\n3. A **server provider** is one that lists `secrets` (credentials a browser\n cannot hold). One with a `browserOptions` entry (an in-browser model such as\n `webllm`) is satisfied in a browser and changes nothing. Otherwise it is\n **mockable** when its `options` include `mock`. A *required* server provider\n that is neither is `sample` when the recipe has a `demoSeed` (the fixtures\n stand in for what the provider would feed), else `server`. An optional one\n that is neither leaves the mode alone and adds a reason.\n4. Any mockable server provider makes the recipe `mock` and lands in `mocked`.\n5. Otherwise `live`.\n\n**Contradictions fail the build** (like `assertRecipeOptions`), naming the\nrecipe: `runtime: 'browser'` on a `server-only` package, or beside a required\nserver provider with no in-browser and no `mock` option. Fix by giving the\nprovider a `browserOptions` entry or a `mock` option, making it optional, or\ndeclaring `both`/`server`. An explicit `both` or\n`server` on a `server-only` package is not a contradiction.\n\n**Dependencies between recipes.** `demo` is the recipe's own answer. A host that\nwants the answer including what the recipe needs calls\n`effectiveRecipeDemo(id, recipes)` (exported from both core entries): the worst\nof its own mode and every `requires` (transitively), counting the best known\nalternative of each `requiresAny` list; unknown ids are ignored.\n\n**Importing it in a browser page.** Use the subpath\n`@happyvertical/smrt-core/recipe-demo` (`deriveRecipeDemo`,\n`effectiveRecipeDemo`, `RECIPE_DEMO_MODES` and the demo types). Its module graph\nis `src/recipe-demo.ts` alone, with type-only imports, so it carries no `node:`\nimport. The root entry and `/browser` both also export these names, but both\nreach `manifest/manifest-loader.ts` through the registry, and that module calls\n`createRequire` from `node:module` as it loads, which a browser page cannot\nsatisfy. `src/__tests__/recipe-demo-entry.test.ts` walks the module graph and\nfails if the subpath gains a `node:` or package import.\n\nThe classification runs in `ManifestGenerator.applyGenerationPasses` (the one\npass sequence the Vite plugin, `ManifestBuilder` and `generateManifest()` share)\nthrough `applyRecipeDemo` in `src/scanner/recipe-demo-pass.ts`, after the\nmerged-manifest recipe assertions. It needs the package name, so it cannot run\nin the scanner, which resolves recipes before it knows the package.\n\n## How the scanner collects it\n\nThe scanner class pass finds classes by decorator, which a recipe lacks, so\n`recipes.ts` is a second matcher in the style of the agent-surface one: a class\nwhose `extends` resolves to the `SmrtRecipe` import binding from\n`@happyvertical/smrt-core` (named, aliased `SmrtRecipe as R`, or namespace\n`Core.SmrtRecipe`; any core subpath such as `/browser`). A same-named local\nclass or an import from another library is not a recipe. A recipe must extend\n`SmrtRecipe` **directly**. Within one file, extending another recipe or declaring\none as a class expression or inside a block is a scan error rather than a silent\ndrop. Across files it is not detected: a module that never names `SmrtRecipe`\nis not read, so a recipe extending a recipe or intermediate base imported from\nanother module is unsupported and would be missing from the artifacts.\n\nClass references resolve to qualified names (`@scope/pkg:Class`) through the\nimport binding: a relative specifier picks the scanned class by file, then by\nclass name (barrel re-exports keep the name); an aliased import follows the\n*imported* name; `Ns.Class` namespace members and same-file classes work; a\nbare package specifier is rejected (models must belong to this package). When\nthe imported file declares no such class, the barrel fallback binds a same-named\nscanned class, so keep model class names unique within a package. This\nwas chosen over qualified-name strings because it type-checks and survives\nrenames; strings were not needed.\n\nRecipe discovery is independent of the model `include` glob (like intents; it is\nnot gated by the `agentSurface` option), and\n`ScanResults.recipes` is populated by `resolve()`. Every error is a scan error,\nso every producer that reads `scanAndResolve()` (`smrtPlugin`, `ManifestBuilder`)\nfails the build. (The legacy `ManifestGenerator.generateManifest()` path takes\npre-scanned results and never sees recipes.) The adapter qualifies names and emits a top-level `recipes` array in\n`manifest.json`; `buildDomainKnowledgeManifest` projects the same array into\n`smrt-knowledge.json`. A package with no recipe emits no key.\n\n## Build-time validation\n\nIds are dotted lowercase and unique; `models` is non-empty, resolvable, and\nduplicate-free; every `nav` model is in `models`; `requires` are well-formed,\nnot self-referential or duplicated, and acyclic within the package (ids in other\npackages cannot be checked at scan time); `options` name only listed models and\nand only the keys below. Field names are checked in core\n(`ManifestGenerator.assertRecipeOptions`, after inherited fields merge) against\nthe merged manifest, so STI-merged, tenant, and framework-base fields such as\n`parentId` are accepted along with the universal `id`, `slug`, `context`,\n`created_at`, `updated_at`; anything else fails the build.\n\n## options: curation hints, never a second schema\n\nKeyed by model class name. `fields.<name>` accepts the smrt-fields policy\nvocabulary: `default`, `label`, `help`, `order`, `visibility`\n(`basic | advanced | hidden`), `locked`; applying a recipe produces\nfield-policy rows. `exposure.<api|mcp|cli>` accepts only `false` or\nnon-empty `{ exclude: [...] }` naming a CRUD verb (`list`, `get`, `create`,\n`update`, `delete`) or a public instance method the model declares or inherits: `true` and `include` are\nrejected because they can widen what the model already declares. Custom fields are out of scope.\n\n## User-facing help (#3591)\n\nHelp is documentation for the people using the app, not developers. It lives\nbeside the recipe as Markdown and is pointed to by `static help`:\n\n```ts\nexport class SalesRecipe extends SmrtRecipe {\n // ...\n static help = './sales.recipe.md'; // relative to the recipe file, .md only\n}\n```\n\nThe file holds an **Overview** and **Tasks** (short how-tos). A step names a\nfield as `{field:<fieldName>}` or `{field:<Model>.<fieldName>}`; the Markdown\nblock (heading, paragraph, or list item) containing a reference is **tied to\nthat field**. Keep prose in the separate file rather than the recipe class.\nField text itself comes from `@field({ description })`, the user-facing seed\nsmrt-fields uses for form help; every field a recipe shows should carry one.\n\n**Emission.** The scanner reads the file (a missing, empty, non-`.md`, absolute\nor `..`-climbing path is a scan error) and emits\n`help: { markdown, fieldRefs }` on the recipe entry in `manifest.json` and\n`smrt-knowledge.json`, so a static host needs no further request.\n`fieldRefs` (distinct references as written, sorted) is **derived**, never\nauthored. Knowledge fields also carry `description` (sensitive fields stay\nexcluded). `ManifestGenerator.assertRecipeHelp` fails the build on a reference\nto a field the recipe's models do not declare, and on a `fieldRefs` list that\ndisagrees with the Markdown. The scanner has its own copy of the reference\ngrammar (`deriveHelpFieldRefs`; it cannot import core), so that comparison also\nkeeps the two from drifting. A reference to a **sensitive** or universal (`id`,\n`slug`, ...) field also fails the build: neither reaches the knowledge artifact,\nso a host building its field list from it would silently drop the step naming\none; describe such a field in prose without a reference.\n\nKnown limitation: the help file is a generator input, but editing only it does\nnot trigger a dev-server rescan (the watch matches source globs) and generation\nsnapshots do not digest it, so rebuild (or touch a `.ts` source) after editing.\n\n**Rendering.** `src/recipe-help.ts` is pure and browser-safe (exported from\nboth core entries). `renderHelp(help, models, options?)` takes the recipe's\nhelp plus the app's effective field policies (`HelpModel[]`; label, help,\nvisibility, disabled in smrt-fields vocabulary, with `@field` descriptions) and\nreturns `{ blocks, glossary, connect? }`:\n\n- `{field:x}` becomes the effective label;\n- a block tied to a hidden, disabled, undeclared, or **advanced** field (unless\n `showAdvanced`) is dropped; a list item drops alone; a heading whose section\n had content that was all dropped is pruned (a heading that never had content\n stays);\n- the glossary lists each shown field with its effective help, else its\n description. A shown field with neither is **skipped**, not listed blank;\n- `connect` is the collapsed \"Connect other tools\" section: the recipe's\n generated REST routes, MCP tools and CLI commands (`options.surfaces`, from\n the knowledge artifact), minus the recipe's `exposure` narrowing\n (`options.exposure`). Hosts render it last.\n\nThe result is an AST of text nodes (`text`, `code`, `strong`, `em`), never HTML,\nso authored markup stays inert text and nothing needs sanitizing. Only\n`##`-`####` headings, paragraphs, flat lists and bold/italic/code are\nunderstood. `helpToMarkdown(rendered)` flattens it for hosts with their own\nrenderer; render that with raw HTML disabled. `validateHelp(help, models)`\nreturns the build-failure problems for hand-built entries.\n"
|
|
1236
|
+
"content": "# SmrtRecipe: declared units of app functionality (#3590)\n\nA **recipe** is a small, named, user-facing unit (\"Sales\", \"Customers\"),\nsmaller than a package. An app or agent adds a recipe and gets that section:\nnavigation, list and create/edit views, and its prerequisites, without every\nmodel in the owning package. Source: `src/recipe.ts` (authoring base class),\n`packages/scanner/src/recipes.ts` (matcher and validation), types in\n`@happyvertical/smrt-types` (`RecipeDefinition`).\n\n## Authoring\n\n`SmrtRecipe` is an abstract, **non-persisted, non-`@smrt()`** base class\nexported from both core entries (`index.ts` and `browser.ts`). A recipe is a\nsubclass with static properties, in the package that owns its models:\n\n```ts\nexport class SalesRecipe extends SmrtRecipe {\n static id = 'commerce.sales'; // dotted lowercase, unique\n static label = 'Sales';\n static summary = 'Take customer orders and track them.';\n static synonyms = ['sales orders', 'orders'];\n static models = [Order]; // class references\n static nav = [{ label: 'Sales Orders', model: Order }];\n static requires = ['commerce.customers']; // recipe ids, any package\n static options = { Order: { fields: { status: { default: 'draft' } } } };\n}\n```\n\nStatics are read **structurally** (nothing is evaluated), so each must be\nspelled literally; `models` and `nav[].model` take identifiers. Write\n`static options = { ... } as const` so `visibility` stays a literal type.\nThe emitted `options` are keyed by qualified model name.\n\n## Host metadata: group, section, nav detail, alternatives (#3604)\n\nEverything a catalog host (an app shell, an agent, a CLI) needs\nto present a recipe lives on the recipe, so it reads the manifest or knowledge\nartifact instead of importing runtime code. All are optional and additive:\n\n| Static | Shape | Meaning |\n| --- | --- | --- |\n| `group` | `{ id, label, summary? }` | Recipes with the same `group.id` share one card; the first declaration of an id supplies `label` and `summary`. Declaration order is the sub-switch order. |\n| `section` | `{ id, label, icon?, description? }` | Navigation section the recipe suggests for its `nav` entries. The host owns sections and the user may rename them, so keep `id` stable; the same `id` shares a section. |\n| `requiresAny` | `string[][]` | At least one id of each inner list must be on (`[['products.simple', 'products.clothing']]`); adding the recipe with none on adds the first. |\n| `nav[].icon` / `description` | string | Shell icon name and one friendly line for a business owner. The icon must be a key of `SHELL_ICON_PATHS` (smrt-svelte `admin-shell/shell-icons.ts`, camelCase such as `fileText` or `shoppingBag`); the same holds for `section.icon`. `packages/scanner/src/__tests__/workspace-recipes.test.ts` fails on any other name. |\n| `nav[].noun` | string | What \"New\" creates when the label is not countable (\"Stock levels\" -> `stock entry`). |\n| `nav[].key` | slug | Fixed key for the layout id `item:<pkg>:<Model>:<key>`. Required for a second entry over the same model and for a `filter`. |\n| `nav[].filter` | `{ field, value }` | Narrows the entry to rows where `field` equals `value` (Ingredients over Products: `productType` = `material`). Needs a `key`. |\n| `options.<Model>.fields.<f>.required` | boolean | The form refuses to save without a value. |\n\nAcross recipes of one package the scanner also rejects a repeated nav `key`\nover the same model (it names one layout id) and one `group`/`section` id with\ntwo different labels. `packages/scanner/src/__tests__/workspace-recipes.test.ts`\nscans every recipe in the workspace and checks ids, `requires`/`requiresAny`\ntargets (which a single package cannot see) and shared labels.\n\nChild models (line items) are simply listed in `models` without a `nav` entry.\nValidation: `group`/`section` take only their keys, slug ids, non-empty\nstrings; `requiresAny` ids follow the recipe-id format and cannot name the\nrecipe itself; each (model, key) pair appears once in `nav`; a `filter.field`\nmust be a field the model declares (checked in core on the merged manifest,\n`assertRecipeOptions`).\n\nNot part of the recipe declaration (still host-local): a host's\ncross-package `forms` and `extends` (form records spanning profiles, variants\nand SKUs).\n\nUser-facing field text for the recipes comes from `@field({ description })` on\nthe models (the glossary seed), so a package that declares a recipe carries the\ndescriptions of every field the recipe shows. A field the package does not\ndeclare itself (`parentId` from `SmrtHierarchical`) is described by the recipe's\n`options.<Model>.fields.<f>.help`, which wins over the description in the\nglossary. Write all of it in plain words: no API, JSON, REST, MCP, SQL or UUID\noutside help that is about connecting other tools.\n\n## Non-model surfaces, providers, runtime, demo seed (#3708)\n\nMany features are not a list of models: a shell widget (assistant dock,\nnotification bell), a component, a route, a settings panel. All four statics are\noptional and additive; a recipe that declares none emits none of the keys. They\nare emitted on the recipe entry in `manifest.json` and `smrt-knowledge.json`.\nComponents are named by `'<package specifier>#<ExportName>'` and never imported\nat scan time; a host resolves them. Relative or absolute specifiers are rejected.\n\n| Static | Shape | Meaning |\n| --- | --- | --- |\n| `surfaces` | `RecipeSurface[]` | `{ kind: 'shell-widget', slot, export, label, icon? }`, `{ kind: 'route', path, export, label }`, `{ kind: 'settings-panel', export, label }`, `{ kind: 'playground', export, label? }`, `{ kind: 'widget', type, export, label, ... }` (see below). `slot` is a smrt-svelte `ShellSlot`; `path` starts with `/` and has no whitespace, `?`, `#` or `..`. |\n| `providers` | `RecipeProvider[]` | `{ id, kind, options, required, secrets?, browserOptions? }`: lowercase slugs, `options` non-empty and distinct, `required` always written, `secrets` distinct `UPPER_SNAKE` names (never values), `browserOptions` a non-empty subset of `options` that run in a browser with none of the secrets (#3709). Ids are unique per recipe. |\n| `runtime` | `'browser' \\| 'server' \\| 'both'` | Where the recipe's runtime pieces can run. Omitted means `both`; the value is emitted as authored. The build refines it into `demo` (below). |\n| `exposure` | `'ui-only'` | The recipe's models are closed to the generated API, MCP and CLI on purpose (employment records). A host still offers the recipe and builds its screens, and the planner's Features card says agents cannot read it. The scanner rejects it unless every model sets `api`, `mcp` and `cli` to `false` or `{ include: [] }`, so the flag cannot drift (#4440). Omit it for every other recipe. |\n| `demoSeed` | `{ export } \\| { data }` | A fixture export reference, or inline JSON of at most 8 KB, for demo hosts. Exactly one key. |\n\nWrite `surfaces` with `as const` (like `options`) so `kind` and `slot` stay\nliteral types. The slot union is `RecipeShellSlot` in smrt-types (types only) and the\nscanner's `RECIPE_SHELL_SLOTS` list copies smrt-svelte's `SHELL_SLOTS`; a\ntype-level test in smrt-svelte and a scanner test keep them equal, so there is\nno runtime dependency on smrt-svelte. Inline `demoSeed.data` must be finite JSON. Every violation (unknown\nkind or slot, extra key, duplicate route path or provider id, malformed\nreference) is a scan error. Example: `events.calendar` in\n`packages/events/src/recipes.ts`.\n\n## Widget surfaces (#3727)\n\n`{ kind: 'widget', ... }` contributes a widget to customizable overviews\n(smrt-svelte `./overview`). The manifest carries everything a host needs to\nregister it; the helper `registerRecipeWidgets` in smrt-svelte does that with an\ninjected module resolver, so nothing is imported at scan time.\n\n```ts\nstatic surfaces = [\n {\n kind: 'widget',\n type: 'sales-total', // registry type, unique per package\n export: '@acme/shop/svelte#SalesTotal', // the component\n label: 'Sales total', // text or an i18n key\n description: 'Order totals for the period.',\n icon: 'chart',\n version: 2, // option-schema version (default 1)\n migrate: '@acme/shop/server#migrateSalesTotal', // needs version above 1\n optionsEditor: '@acme/shop/svelte#SalesOptions', // optional dependent-choice editor\n options: [\n { key: 'model', type: 'model', label: 'Model', required: true },\n { key: 'style', type: 'enum', label: 'Style', default: 'bar',\n choices: [{ value: 'bar', label: 'Bar' }, { value: 'line', label: 'Line' }] },\n ],\n data: { load: '@acme/shop/server#loadSalesTotal', models: ['@acme/shop:Order'] },\n allowedIn: ['shop.home'], // overview ids; omit for any\n sectionDefault: true, // optional: this widget IS the section page\n defaultSpan: 2, minSpan: 1, maxSpan: 3,\n },\n] as const;\n```\n\nEmitted keys, in this order: `kind`, `type`, `export`, `label`, `description`,\n`icon`, `version`, `migrate`, `optionsEditor`, `options`, `data`, `allowedIn`,\n`sectionDefault`, `defaultSpan`, `minSpan`, `maxSpan`; absent ones are omitted. Rules (each violation is a scan\nerror, same strictness as the other kinds):\n\n- `type` is a lowercase kebab id (`[a-z][a-z0-9-]{0,31}`), unique among a\n recipe's surfaces and across the package's recipes. `export`, `data.load`,\n `migrate`, and `optionsEditor` are `'<package specifier>#<ExportName>'`;\n relative specifiers are rejected. `label` is required. A UI host resolves an\n `optionsEditor` lazily for dependent or remote choices; the server registry\n never imports it, and every saved value still passes the ordinary option schema.\n- `options` is a non-empty list of plain-JSON fields limited to the overview\n option types: `text`, `markdown`, `identifier`, `model`, `integer`, `number`,\n `boolean`, closed `enum` (non-empty distinct `choices`). There is no free-form\n query, expression, URL or SQL type. Keys are camelCase (`[A-Za-z][A-Za-z0-9]{0,31}`)\n and unique; `default` must pass its field's own rule; `min`/`max` only on\n numbers, `maxLength` only on text. Only `key`, `type`, `label`, `help`,\n `required`, `default`, `min`, `max`, `maxLength`, `choices` are accepted.\n- `sectionDefault: true` makes the widget the section page of the recipe's\n section: when the recipe leads a navigation section (the first recipe\n suggesting it), a host rendering data widgets defaults that section's\n overview to this widget alone, full width, instead of the generic shortcuts,\n count and latest records (smrt-svelte `sectionDefaultWidget`). At most one per\n recipe, and every required option needs a `default`, since it is placed with\n no options. The person can still edit the page.\n- `data` takes `load` and/or `models` (qualified class names, distinct). The\n scanner cannot see other packages, so a model is checked for format only.\n- `allowedIn` is a non-empty list of distinct overview ids\n (`[A-Za-z0-9][A-Za-z0-9_.:-]{0,95}`); spans are integers 1 to 4 with\n `minSpan <= defaultSpan <= maxSpan`.\n\nThe scanner's copies of these rules live in `packages/scanner/src/recipe-widgets.ts`\nand a scanner test compares them to the smrt-svelte source. See\n[overview-surfaces.md](../../smrt-svelte/agents/overview-surfaces.md#recipe-widgets-recipe-widgetsts)\nfor how a host registers them.\n\n## Browser-demo mode: Live, Mock, Sample, Server (#3709)\n\nA catalog host (smrt-planner) labels each feature by how far it runs in a\nbrowser-only demo. The label is **derived at build time**, never authored: the\nmanifest build combines what the bundle-gate measured for the recipe's package\nwith the recipe's own `runtime`, `providers` and `demoSeed`, and emits it as\n`demo` on the recipe entry and `browser` on the package. A recipe that declares\nnone of those keeps its shape except for `demo`, which every recipe of a\nmeasured package gets.\n\n```jsonc\n// manifest.json and smrt-knowledge.json, top level\n\"browser\": {\n \"status\": \"browser-safe\" | \"server-only\",\n \"issues\": [\"#3624\"], // tracking issues; omitted when browser-safe\n \"reason\": \"node:crypto\", // the package's own blocker; omitted when only inherited\n \"via\": [\"@happyvertical/smrt-tenancy\"] // inherited from these; omitted when none\n}\n// on each entry of \"recipes\"\n\"demo\": {\n \"mode\": \"live\" | \"mock\" | \"sample\" | \"server\",\n \"reasons\": [\"Needs a server because ...\"], // plain sentences, in rule order\n \"mocked\": [\"smtp\"] // providers a demo fakes; omitted when none\n}\n```\n\n| Mode | Meaning |\n| --- | --- |\n| `live` | Real browser data layer (PGlite); nothing faked. |\n| `mock` | Runs in the browser, but a provider (sending mail, OAuth) is faked. |\n| `sample` | Runs in the browser on `demoSeed` fixtures only. |\n| `server` | Needs a server; a browser-only host cannot offer it. |\n\n**Package capability.** The browser gate (`packages/bundle-gate`) fails any\nmodel package that is not in its expected-failures list and does not build for\na browser, and any listed package that now builds. So a package is `server-only`\nexactly when it is listed. The ratchet attributes a defect to the package that\nowns the fix, so a dependent passes while still dragging the failure into a\nbrowser bundle; the derivation therefore also marks a package `server-only`\nwhen a workspace package it depends on (transitively, through `dependencies`)\nis, and records them in `via`. `smrt-core` is the platform baseline and does not\npropagate. The table lives in `src/manifest/browser-capability.generated.ts`\n(generated, never edited) and a bundle-gate spec fails when it is stale; see\nthat package's AGENTS.md for the regenerate command. A package the gate does\nnot measure (an app's own, a UI package) emits no `browser`, and its recipes get\na `demo` only when they declare `runtime`, `providers` or `demoSeed`.\n\n**Rules** (`src/recipe-demo.ts`, pure and browser-safe; the worst mode wins,\n`live` < `mock` < `sample` < `server`):\n\n1. `runtime: 'server'` is `server`. `runtime: 'both'` or omitted makes no claim:\n the package capability refines it, so `both` on a `server-only` package is\n `server`.\n2. A `server-only` package is `server`.\n3. A **server provider** is one that lists `secrets` (credentials a browser\n cannot hold). One with a `browserOptions` entry (an in-browser model such as\n `webllm`) is satisfied in a browser and changes nothing. Otherwise it is\n **mockable** when its `options` include `mock`. A *required* server provider\n that is neither is `sample` when the recipe has a `demoSeed` (the fixtures\n stand in for what the provider would feed), else `server`. An optional one\n that is neither leaves the mode alone and adds a reason.\n4. Any mockable server provider makes the recipe `mock` and lands in `mocked`.\n5. Otherwise `live`.\n\n**Contradictions fail the build** (like `assertRecipeOptions`), naming the\nrecipe: `runtime: 'browser'` on a `server-only` package, or beside a required\nserver provider with no in-browser and no `mock` option. Fix by giving the\nprovider a `browserOptions` entry or a `mock` option, making it optional, or\ndeclaring `both`/`server`. An explicit `both` or\n`server` on a `server-only` package is not a contradiction.\n\n**Dependencies between recipes.** `demo` is the recipe's own answer. A host that\nwants the answer including what the recipe needs calls\n`effectiveRecipeDemo(id, recipes)` (exported from both core entries): the worst\nof its own mode and every `requires` (transitively), counting the best known\nalternative of each `requiresAny` list; unknown ids are ignored.\n\n**Importing it in a browser page.** Use the subpath\n`@happyvertical/smrt-core/recipe-demo` (`deriveRecipeDemo`,\n`effectiveRecipeDemo`, `RECIPE_DEMO_MODES` and the demo types). Its module graph\nis `src/recipe-demo.ts` alone, with type-only imports, so it carries no `node:`\nimport. The root entry and `/browser` both also export these names, but both\nreach `manifest/manifest-loader.ts` through the registry, and that module calls\n`createRequire` from `node:module` as it loads, which a browser page cannot\nsatisfy. `src/__tests__/recipe-demo-entry.test.ts` walks the module graph and\nfails if the subpath gains a `node:` or package import.\n\nThe classification runs in `ManifestGenerator.applyGenerationPasses` (the one\npass sequence the Vite plugin, `ManifestBuilder` and `generateManifest()` share)\nthrough `applyRecipeDemo` in `src/scanner/recipe-demo-pass.ts`, after the\nmerged-manifest recipe assertions. It needs the package name, so it cannot run\nin the scanner, which resolves recipes before it knows the package.\n\n## How the scanner collects it\n\nThe scanner class pass finds classes by decorator, which a recipe lacks, so\n`recipes.ts` is a second matcher in the style of the agent-surface one: a class\nwhose `extends` resolves to the `SmrtRecipe` import binding from\n`@happyvertical/smrt-core` (named, aliased `SmrtRecipe as R`, or namespace\n`Core.SmrtRecipe`; any core subpath such as `/browser`). A same-named local\nclass or an import from another library is not a recipe. A recipe must extend\n`SmrtRecipe` **directly**. Within one file, extending another recipe or declaring\none as a class expression or inside a block is a scan error rather than a silent\ndrop. Across files it is not detected: a module that never names `SmrtRecipe`\nis not read, so a recipe extending a recipe or intermediate base imported from\nanother module is unsupported and would be missing from the artifacts.\n\nClass references resolve to qualified names (`@scope/pkg:Class`) through the\nimport binding: a relative specifier picks the scanned class by file, then by\nclass name (barrel re-exports keep the name); an aliased import follows the\n*imported* name; `Ns.Class` namespace members and same-file classes work; a\nbare package specifier is rejected (models must belong to this package). When\nthe imported file declares no such class, the barrel fallback binds a same-named\nscanned class, so keep model class names unique within a package. This\nwas chosen over qualified-name strings because it type-checks and survives\nrenames; strings were not needed.\n\nRecipe discovery is independent of the model `include` glob (like intents; it is\nnot gated by the `agentSurface` option), and\n`ScanResults.recipes` is populated by `resolve()`. Every error is a scan error,\nso every producer that reads `scanAndResolve()` (`smrtPlugin`, `ManifestBuilder`)\nfails the build. (The legacy `ManifestGenerator.generateManifest()` path takes\npre-scanned results and never sees recipes.) The adapter qualifies names and emits a top-level `recipes` array in\n`manifest.json`; `buildDomainKnowledgeManifest` projects the same array into\n`smrt-knowledge.json`. A package with no recipe emits no key.\n\n## Build-time validation\n\nIds are dotted lowercase and unique (the first segment is usually a package short\nname and may be hyphenated: `human-resources.people`); `models` is non-empty, resolvable, and\nduplicate-free; every `nav` model is in `models`; `requires` are well-formed,\nnot self-referential or duplicated, and acyclic within the package (ids in other\npackages cannot be checked at scan time); `options` name only listed models and\nand only the keys below. Field names are checked in core\n(`ManifestGenerator.assertRecipeOptions`, after inherited fields merge) against\nthe merged manifest, so STI-merged, tenant, and framework-base fields such as\n`parentId` are accepted along with the universal `id`, `slug`, `context`,\n`created_at`, `updated_at`; anything else fails the build.\n\n## options: curation hints, never a second schema\n\nKeyed by model class name. `fields.<name>` accepts the smrt-fields policy\nvocabulary: `default`, `label`, `help`, `order`, `visibility`\n(`basic | advanced | hidden`), `locked`; applying a recipe produces\nfield-policy rows. `exposure.<api|mcp|cli>` accepts only `false` or\nnon-empty `{ exclude: [...] }` naming a CRUD verb (`list`, `get`, `create`,\n`update`, `delete`) or a public instance method the model declares or inherits: `true` and `include` are\nrejected because they can widen what the model already declares. Custom fields are out of scope.\n\n## User-facing help (#3591)\n\nHelp is documentation for the people using the app, not developers. It lives\nbeside the recipe as Markdown and is pointed to by `static help`:\n\n```ts\nexport class SalesRecipe extends SmrtRecipe {\n // ...\n static help = './sales.recipe.md'; // relative to the recipe file, .md only\n}\n```\n\nThe file holds an **Overview** and **Tasks** (short how-tos). A step names a\nfield as `{field:<fieldName>}` or `{field:<Model>.<fieldName>}`; the Markdown\nblock (heading, paragraph, or list item) containing a reference is **tied to\nthat field**. Keep prose in the separate file rather than the recipe class.\nField text itself comes from `@field({ description })`, the user-facing seed\nsmrt-fields uses for form help; every field a recipe shows should carry one.\n\n**Emission.** The scanner reads the file (a missing, empty, non-`.md`, absolute\nor `..`-climbing path is a scan error) and emits\n`help: { markdown, fieldRefs }` on the recipe entry in `manifest.json` and\n`smrt-knowledge.json`, so a static host needs no further request.\n`fieldRefs` (distinct references as written, sorted) is **derived**, never\nauthored. Knowledge fields also carry `description` (sensitive fields stay\nexcluded). `ManifestGenerator.assertRecipeHelp` fails the build on a reference\nto a field the recipe's models do not declare, and on a `fieldRefs` list that\ndisagrees with the Markdown. The scanner has its own copy of the reference\ngrammar (`deriveHelpFieldRefs`; it cannot import core), so that comparison also\nkeeps the two from drifting. A reference to a **sensitive** or universal (`id`,\n`slug`, ...) field also fails the build: neither reaches the knowledge artifact,\nso a host building its field list from it would silently drop the step naming\none; describe such a field in prose without a reference.\n\nKnown limitation: the help file is a generator input, but editing only it does\nnot trigger a dev-server rescan (the watch matches source globs) and generation\nsnapshots do not digest it, so rebuild (or touch a `.ts` source) after editing.\n\n**Rendering.** `src/recipe-help.ts` is pure and browser-safe (exported from\nboth core entries). `renderHelp(help, models, options?)` takes the recipe's\nhelp plus the app's effective field policies (`HelpModel[]`; label, help,\nvisibility, disabled in smrt-fields vocabulary, with `@field` descriptions) and\nreturns `{ blocks, glossary, connect? }`:\n\n- `{field:x}` becomes the effective label;\n- a block tied to a hidden, disabled, undeclared, or **advanced** field (unless\n `showAdvanced`) is dropped; a list item drops alone; a heading whose section\n had content that was all dropped is pruned (a heading that never had content\n stays);\n- the glossary lists each shown field with its effective help, else its\n description. A shown field with neither is **skipped**, not listed blank;\n- `connect` is the collapsed \"Connect other tools\" section: the recipe's\n generated REST routes, MCP tools and CLI commands (`options.surfaces`, from\n the knowledge artifact), minus the recipe's `exposure` narrowing\n (`options.exposure`). Hosts render it last.\n\nThe result is an AST of text nodes (`text`, `code`, `strong`, `em`), never HTML,\nso authored markup stays inert text and nothing needs sanitizing. Only\n`##`-`####` headings, paragraphs, flat lists and bold/italic/code are\nunderstood. `helpToMarkdown(rendered)` flattens it for hosts with their own\nrenderer; render that with raw HTML disabled. `validateHelp(help, models)`\nreturns the build-failure problems for hand-built entries.\n"
|
|
1237
1237
|
},
|
|
1238
1238
|
{
|
|
1239
1239
|
"path": "agents/cookbook.md",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-core",
|
|
3
|
-
"version": "0.57.
|
|
3
|
+
"version": "0.57.35",
|
|
4
4
|
"description": "Core AI agent framework with standardized collections, object-relational mapping, and code generators",
|
|
5
5
|
"author": "HappyVertical",
|
|
6
6
|
"type": "module",
|
|
@@ -199,9 +199,9 @@
|
|
|
199
199
|
"@happyvertical/files": "^0.108.0",
|
|
200
200
|
"@happyvertical/json": "^0.108.0",
|
|
201
201
|
"@happyvertical/logger": "^0.108.0",
|
|
202
|
-
"@happyvertical/smrt-config": "0.57.
|
|
203
|
-
"@happyvertical/smrt-scanner": "0.57.
|
|
204
|
-
"@happyvertical/smrt-types": "0.57.
|
|
202
|
+
"@happyvertical/smrt-config": "0.57.35",
|
|
203
|
+
"@happyvertical/smrt-scanner": "0.57.35",
|
|
204
|
+
"@happyvertical/smrt-types": "0.57.35",
|
|
205
205
|
"@happyvertical/sql": "^0.108.0",
|
|
206
206
|
"@happyvertical/utils": "^0.108.0",
|
|
207
207
|
"@noble/hashes": "^2.4.0",
|