@happyvertical/smrt-core 0.55.7 → 0.55.9

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.
Files changed (203) hide show
  1. package/AGENTS.md +2 -1
  2. package/agents/browser-entry.md +44 -0
  3. package/agents/build-knowledge.md +7 -0
  4. package/agents/recipes.md +217 -0
  5. package/agents/revision-guard.md +72 -1
  6. package/dist/audit-context.d.ts.map +1 -1
  7. package/dist/audit-context.js +2 -2
  8. package/dist/audit-context.js.map +1 -1
  9. package/dist/browser.d.ts +2 -1
  10. package/dist/browser.d.ts.map +1 -1
  11. package/dist/browser.js +7 -7
  12. package/dist/cascade.d.ts +1 -1
  13. package/dist/cascade.d.ts.map +1 -1
  14. package/dist/cascade.js +1 -1
  15. package/dist/cascade.js.map +1 -1
  16. package/dist/change-feed.d.ts.map +1 -1
  17. package/dist/change-feed.js +1 -2
  18. package/dist/change-feed.js.map +1 -1
  19. package/dist/class.d.ts +1 -1
  20. package/dist/class.d.ts.map +1 -1
  21. package/dist/class.js +7 -3
  22. package/dist/class.js.map +1 -1
  23. package/dist/collection.d.ts.map +1 -1
  24. package/dist/collection.js +1 -1
  25. package/dist/collection.js.map +1 -1
  26. package/dist/consumer-plugin/index.js +2 -2
  27. package/dist/data-query.d.ts.map +1 -1
  28. package/dist/data-query.js +2 -13
  29. package/dist/data-query.js.map +1 -1
  30. package/dist/database.js +1 -1
  31. package/dist/database.js.map +1 -1
  32. package/dist/dispatch/bus.js +1 -1
  33. package/dist/dispatch/bus.js.map +1 -1
  34. package/dist/embedded-write-queue.d.ts +1 -1
  35. package/dist/embedded-write-queue.d.ts.map +1 -1
  36. package/dist/embedded-write-queue.js +7 -5
  37. package/dist/embedded-write-queue.js.map +1 -1
  38. package/dist/embeddings/hash.js +2 -2
  39. package/dist/embeddings/hash.js.map +1 -1
  40. package/dist/embeddings/storage.d.ts.map +1 -1
  41. package/dist/embeddings/storage.js +1 -2
  42. package/dist/embeddings/storage.js.map +1 -1
  43. package/dist/errors.d.ts.map +1 -1
  44. package/dist/errors.js +1 -0
  45. package/dist/errors.js.map +1 -1
  46. package/dist/generation-snapshot.js +1 -1
  47. package/dist/generators/index.js +1 -1
  48. package/dist/generators/mcp.js +3 -3
  49. package/dist/generators/preflight-route.js +1 -1
  50. package/dist/generators/rest.js +1 -1
  51. package/dist/generators.js +1 -1
  52. package/dist/host.browser.d.ts +12 -0
  53. package/dist/host.browser.d.ts.map +1 -0
  54. package/dist/host.browser.js +30 -0
  55. package/dist/host.browser.js.map +1 -0
  56. package/dist/host.d.ts +18 -0
  57. package/dist/host.d.ts.map +1 -0
  58. package/dist/host.js +15 -0
  59. package/dist/host.js.map +1 -0
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +10 -9
  63. package/dist/knowledge.d.ts.map +1 -1
  64. package/dist/knowledge.js +5 -4
  65. package/dist/knowledge.js.map +1 -1
  66. package/dist/manifest/browser-capability.generated.d.ts +14 -0
  67. package/dist/manifest/browser-capability.generated.d.ts.map +1 -0
  68. package/dist/manifest/browser-capability.generated.js +499 -0
  69. package/dist/manifest/browser-capability.generated.js.map +1 -0
  70. package/dist/manifest/discover-base-classes.js +1 -1
  71. package/dist/manifest/discover-smrt-packages.js +1 -1
  72. package/dist/manifest/generator.js +3 -3
  73. package/dist/manifest/index.js +2 -2
  74. package/dist/manifest/local-manifest-reader.d.ts +48 -0
  75. package/dist/manifest/local-manifest-reader.d.ts.map +1 -0
  76. package/dist/manifest/local-manifest-reader.js +81 -0
  77. package/dist/manifest/local-manifest-reader.js.map +1 -0
  78. package/dist/manifest/manager.d.ts +5 -28
  79. package/dist/manifest/manager.d.ts.map +1 -1
  80. package/dist/manifest/manager.js +10 -56
  81. package/dist/manifest/manager.js.map +1 -1
  82. package/dist/manifest/manifest-loader.d.ts +1 -1
  83. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  84. package/dist/manifest/manifest-loader.js +43 -12
  85. package/dist/manifest/manifest-loader.js.map +1 -1
  86. package/dist/manifest/node-host.d.ts +38 -0
  87. package/dist/manifest/node-host.d.ts.map +1 -0
  88. package/dist/manifest/node-host.js +52 -0
  89. package/dist/manifest/node-host.js.map +1 -0
  90. package/dist/manifest/package-manifest-exports.js +1 -1
  91. package/dist/manifest/static-manifest.d.ts.map +1 -1
  92. package/dist/manifest/static-manifest.js +7 -2
  93. package/dist/manifest/static-manifest.js.map +1 -1
  94. package/dist/manifest/store.d.ts +2 -1
  95. package/dist/manifest/store.d.ts.map +1 -1
  96. package/dist/manifest/store.js +7 -4
  97. package/dist/manifest/store.js.map +1 -1
  98. package/dist/manifest.js +1 -1
  99. package/dist/manifest.json +9 -2
  100. package/dist/native-duckdb.d.ts +28 -0
  101. package/dist/native-duckdb.d.ts.map +1 -0
  102. package/dist/native-duckdb.js +51 -0
  103. package/dist/native-duckdb.js.map +1 -0
  104. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_md.js +169 -0
  105. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_md.js.map +1 -0
  106. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_u64.js +13 -0
  107. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/_u64.js.map +1 -0
  108. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/sha2.js +198 -0
  109. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/sha2.js.map +1 -0
  110. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/utils.js +243 -0
  111. package/dist/node_modules/.pnpm/@noble_hashes@2.4.0/node_modules/@noble/hashes/utils.js.map +1 -0
  112. package/dist/object.d.ts +56 -0
  113. package/dist/object.d.ts.map +1 -1
  114. package/dist/object.js +139 -16
  115. package/dist/object.js.map +1 -1
  116. package/dist/postgres-permissions.d.ts.map +1 -1
  117. package/dist/postgres-permissions.js +3 -3
  118. package/dist/postgres-permissions.js.map +1 -1
  119. package/dist/prebuild/index.js +1 -1
  120. package/dist/recipe-demo.d.ts +70 -0
  121. package/dist/recipe-demo.d.ts.map +1 -0
  122. package/dist/recipe-demo.js +130 -0
  123. package/dist/recipe-demo.js.map +1 -0
  124. package/dist/recipe.d.ts +64 -1
  125. package/dist/recipe.d.ts.map +1 -1
  126. package/dist/recipe.js +30 -0
  127. package/dist/recipe.js.map +1 -1
  128. package/dist/registry/class-registration.js +2 -2
  129. package/dist/registry/class-registration.js.map +1 -1
  130. package/dist/registry/db-cache-key.js +4 -5
  131. package/dist/registry/db-cache-key.js.map +1 -1
  132. package/dist/registry/inheritance-resolver.js +1 -1
  133. package/dist/registry/name-resolver.js +1 -1
  134. package/dist/registry/qualified-name-aliases.js +1 -1
  135. package/dist/registry/schema-builder.d.ts.map +1 -1
  136. package/dist/registry/schema-builder.js +3 -3
  137. package/dist/registry/schema-builder.js.map +1 -1
  138. package/dist/registry.d.ts.map +1 -1
  139. package/dist/registry.js +5 -8
  140. package/dist/registry.js.map +1 -1
  141. package/dist/revision-guard.js +1 -1
  142. package/dist/revision-guard.js.map +1 -1
  143. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  144. package/dist/scanner/manifest-generator.js +19 -10
  145. package/dist/scanner/manifest-generator.js.map +1 -1
  146. package/dist/scanner/recipe-demo-pass.d.ts +14 -0
  147. package/dist/scanner/recipe-demo-pass.d.ts.map +1 -0
  148. package/dist/scanner/recipe-demo-pass.js +33 -0
  149. package/dist/scanner/recipe-demo-pass.js.map +1 -0
  150. package/dist/scanner/types.d.ts +7 -1
  151. package/dist/scanner/types.d.ts.map +1 -1
  152. package/dist/scanner/types.js.map +1 -1
  153. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  154. package/dist/schema/ddl/duckdb-strategy.js +1 -1
  155. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  156. package/dist/schema/foreign-key-planner.d.ts.map +1 -1
  157. package/dist/schema/foreign-key-planner.js +1 -1
  158. package/dist/schema/foreign-key-planner.js.map +1 -1
  159. package/dist/schema/generator.d.ts.map +1 -1
  160. package/dist/schema/generator.js +10 -14
  161. package/dist/schema/generator.js.map +1 -1
  162. package/dist/schema/schema-aggregator.js +1 -1
  163. package/dist/scripts/browser-entry-evaluation.mjs +157 -0
  164. package/dist/smrt-knowledge.json +27 -12
  165. package/dist/system/browser.d.ts +16 -0
  166. package/dist/system/browser.d.ts.map +1 -0
  167. package/dist/system/browser.js +6 -0
  168. package/dist/system/index.d.ts +1 -6
  169. package/dist/system/index.d.ts.map +1 -1
  170. package/dist/system/index.js +2 -1
  171. package/dist/testing/database.js +1 -1
  172. package/dist/tools/index.js +1 -1
  173. package/dist/unchanged-indexed.d.ts +85 -0
  174. package/dist/unchanged-indexed.d.ts.map +1 -0
  175. package/dist/unchanged-indexed.js +219 -0
  176. package/dist/unchanged-indexed.js.map +1 -0
  177. package/dist/utils/async-context.d.ts +22 -0
  178. package/dist/utils/async-context.d.ts.map +1 -0
  179. package/dist/utils/async-context.js +27 -0
  180. package/dist/utils/async-context.js.map +1 -0
  181. package/dist/utils/import-project-vite.js +1 -1
  182. package/dist/utils/import-workspace-module.js +1 -1
  183. package/dist/utils/json.browser.d.ts +18 -0
  184. package/dist/utils/json.browser.d.ts.map +1 -0
  185. package/dist/utils/json.browser.js +47 -0
  186. package/dist/utils/json.browser.js.map +1 -0
  187. package/dist/utils/sha256.d.ts +15 -0
  188. package/dist/utils/sha256.d.ts.map +1 -0
  189. package/dist/utils/sha256.js +29 -0
  190. package/dist/utils/sha256.js.map +1 -0
  191. package/dist/vite-plugin/changes-route.js +1 -1
  192. package/dist/vite-plugin/dev-plane-route.js +1 -1
  193. package/dist/vite-plugin/events-route.js +1 -1
  194. package/dist/vite-plugin/import-build-aware.js +1 -1
  195. package/dist/vite-plugin/index.js +2 -2
  196. package/dist/vite-plugin/resources-route.js +1 -1
  197. package/dist/vite-plugin/sveltekit-generator.js +2 -2
  198. package/dist/vite-plugin/sveltekit-path.js +1 -1
  199. package/dist/vite-plugin/sveltekit-register-injection.js +1 -1
  200. package/dist/vite-plugin/sveltekit-route-coordinator.js +1 -1
  201. package/dist/vite-plugin/sync-apply-route.js +1 -1
  202. package/dist/vite-plugin/web-collections.js +2 -2
  203. package/package.json +21 -11
@@ -0,0 +1,130 @@
1
+ //#region src/recipe-demo.ts
2
+ /** Modes from best to worst for a demo; used to pick the worst of several. */
3
+ var RECIPE_DEMO_MODES = [
4
+ "live",
5
+ "mock",
6
+ "sample",
7
+ "server"
8
+ ];
9
+ /** The provider option that names a fake a demo can use instead of the real one. */
10
+ var MOCK_PROVIDER_OPTION = "mock";
11
+ var rank = (mode) => RECIPE_DEMO_MODES.indexOf(mode);
12
+ /** True when the provider holds credentials a browser cannot (`secrets` listed). */
13
+ function isServerProvider(provider) {
14
+ return (provider.secrets?.length ?? 0) > 0;
15
+ }
16
+ /** True when one of the provider's options runs in a browser (`browserOptions`). */
17
+ function hasBrowserOption(provider) {
18
+ return (provider.browserOptions?.length ?? 0) > 0;
19
+ }
20
+ /** True when a demo can fake the provider (`options` includes `mock`). */
21
+ function isMockableProvider(provider) {
22
+ return provider.options.includes(MOCK_PROVIDER_OPTION);
23
+ }
24
+ function describePackage(capability) {
25
+ const issues = capability.issues?.length ? ` (${capability.issues.join(", ")})` : "";
26
+ return `${[capability.reason ? `its package does not build for a browser: ${capability.reason}` : void 0, capability.via?.length ? `its package depends on ${capability.via.join(", ")}, which do not build for a browser` : void 0].filter(Boolean).join("; and ") || "its package does not build for a browser"}${issues}`;
27
+ }
28
+ /**
29
+ * Classify one recipe. `capability` is the bundle-gate result for the recipe's
30
+ * package, or `undefined` when the package is not measured (a consumer app's
31
+ * own recipes); then only the recipe's own declarations decide.
32
+ */
33
+ function deriveRecipeDemo(recipe, capability) {
34
+ const problems = [];
35
+ const reasons = [];
36
+ let mode = "live";
37
+ const raise = (next) => {
38
+ if (rank(next) > rank(mode)) mode = next;
39
+ };
40
+ const providers = recipe.providers ?? [];
41
+ const serverProviders = providers.filter((provider) => isServerProvider(provider) && !hasBrowserOption(provider));
42
+ const inBrowser = providers.filter((provider) => isServerProvider(provider) && hasBrowserOption(provider));
43
+ const mockable = serverProviders.filter(isMockableProvider);
44
+ const blocking = serverProviders.filter((provider) => provider.required && !isMockableProvider(provider));
45
+ const optionalUnavailable = serverProviders.filter((provider) => !provider.required && !isMockableProvider(provider));
46
+ const packageServerOnly = capability?.status === "server-only";
47
+ if (recipe.runtime === "browser") {
48
+ if (capability && packageServerOnly) problems.push(`recipe ${recipe.id}: runtime is "browser" but ${describePackage(capability)}; declare "both" or fix the package`);
49
+ for (const provider of blocking) problems.push(`recipe ${recipe.id}: runtime is "browser" but provider "${provider.id}" is required and holds secrets (${provider.secrets?.join(", ")}) a browser cannot keep; give it a browserOptions entry or a "${MOCK_PROVIDER_OPTION}" option, make it optional, or declare "both" or "server"`);
50
+ }
51
+ if (recipe.runtime === "server") {
52
+ raise("server");
53
+ reasons.push("The recipe declares runtime \"server\".");
54
+ }
55
+ if (capability && packageServerOnly) {
56
+ raise("server");
57
+ reasons.push(`Needs a server because ${describePackage(capability)}.`);
58
+ }
59
+ if (blocking.length > 0) {
60
+ const names = blocking.map((provider) => provider.id).join(", ");
61
+ if (recipe.demoSeed) {
62
+ raise("sample");
63
+ reasons.push(`Required provider ${names} holds secrets a browser cannot keep and has no in-browser or "${MOCK_PROVIDER_OPTION}" option; the demo seed stands in for its data.`);
64
+ } else {
65
+ raise("server");
66
+ reasons.push(`Required provider ${names} holds secrets a browser cannot keep and has no in-browser or "${MOCK_PROVIDER_OPTION}" option and the recipe has no demo seed.`);
67
+ }
68
+ }
69
+ if (mockable.length > 0) {
70
+ raise("mock");
71
+ reasons.push(`Provider ${mockable.map((provider) => provider.id).join(", ")} is faked in a demo.`);
72
+ }
73
+ if (inBrowser.length > 0) reasons.push(`Provider ${inBrowser.map((provider) => provider.id).join(", ")} has an in-browser option (${inBrowser.flatMap((provider) => provider.browserOptions ?? []).join(", ")}).`);
74
+ if (optionalUnavailable.length > 0) reasons.push(`Optional provider ${optionalUnavailable.map((provider) => provider.id).join(", ")} is unavailable in a browser.`);
75
+ const declares = recipe.runtime !== void 0 || providers.length > 0 || recipe.demoSeed !== void 0;
76
+ if (!capability && !declares) return { problems };
77
+ if (!capability) reasons.push("The package browser capability is not measured.");
78
+ if (mode === "live" && capability && reasons.length === 0) reasons.push("The package builds for a browser and nothing needs faking.");
79
+ return {
80
+ demo: {
81
+ mode,
82
+ reasons,
83
+ ...mockable.length > 0 ? { mocked: mockable.map((provider) => provider.id) } : {}
84
+ },
85
+ problems
86
+ };
87
+ }
88
+ /**
89
+ * The mode a host should show for `recipeId`: the worst of its own `demo` and
90
+ * that of every recipe it needs. Each `requiresAny` list counts its best known
91
+ * alternative. Ids that are unknown, or whose recipe has no `demo`, are
92
+ * ignored, so a partial catalog degrades to the recipe's own answer. Returns
93
+ * `undefined` when the recipe is unknown or has no `demo` of its own.
94
+ */
95
+ function effectiveRecipeDemo(recipeId, recipes) {
96
+ const byId = new Map(recipes.map((recipe) => [recipe.id, recipe]));
97
+ const resolve = (id, trail) => {
98
+ const recipe = byId.get(id);
99
+ if (!recipe?.demo || trail.includes(id)) return recipe?.demo;
100
+ let mode = recipe.demo.mode;
101
+ const reasons = [...recipe.demo.reasons];
102
+ const consider = (needed, via) => {
103
+ if (rank(via.mode) > rank(mode)) mode = via.mode;
104
+ if (via.mode !== "live") reasons.push(`Needs ${needed}, which is ${via.mode}.`);
105
+ };
106
+ for (const needed of recipe.requires) {
107
+ const demo = resolve(needed, [...trail, id]);
108
+ if (demo) consider(needed, demo);
109
+ }
110
+ for (const alternatives of recipe.requiresAny ?? []) {
111
+ const known = alternatives.map((needed) => ({
112
+ needed,
113
+ demo: resolve(needed, [...trail, id])
114
+ })).filter((entry) => entry.demo !== void 0);
115
+ if (known.length === 0) continue;
116
+ const best = known.reduce((a, b) => rank(b.demo.mode) < rank(a.demo.mode) ? b : a);
117
+ consider(best.needed, best.demo);
118
+ }
119
+ return {
120
+ mode,
121
+ reasons,
122
+ ...recipe.demo.mocked ? { mocked: recipe.demo.mocked } : {}
123
+ };
124
+ };
125
+ return resolve(recipeId, []);
126
+ }
127
+ //#endregion
128
+ export { MOCK_PROVIDER_OPTION, RECIPE_DEMO_MODES, deriveRecipeDemo, effectiveRecipeDemo, hasBrowserOption, isMockableProvider, isServerProvider };
129
+
130
+ //# sourceMappingURL=recipe-demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipe-demo.js","names":[],"sources":["../src/recipe-demo.ts"],"sourcesContent":["/**\n * Browser-demo classification of a recipe (#3709): the pure, browser-safe\n * rules that turn what a recipe declares (`runtime`, `providers`, `demoSeed`)\n * and what the bundle-gate measured for its package into one of four modes.\n *\n * - `live`: the 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 fixture data (`demoSeed`) only.\n * - `server`: needs a server; a browser-only host cannot offer it.\n *\n * The classification is **derived**, never authored: the build calls\n * {@link deriveRecipeDemo} and emits `demo` on the recipe entry. A host that\n * wants the answer for a recipe together with the recipes it needs calls\n * {@link effectiveRecipeDemo}.\n *\n * Rules, in the order they fire (the worst mode wins; `live` < `mock` <\n * `sample` < `server`):\n *\n * 1. `runtime: 'server'` is `server`. `runtime: 'both'` (or omitted) makes no\n * claim, so the package capability refines it.\n * 2. A package the bundle-gate reports `server-only` is `server`.\n * 3. A *server provider* is one that lists `secrets`: it holds credentials a\n * browser cannot. One with a `browserOptions` entry is satisfied in a\n * browser (an in-browser model), so it changes nothing. Otherwise it is\n * *mockable* when `options` includes `mock`. A required server provider\n * that is neither is `sample` when the recipe ships a `demoSeed` (the\n * fixtures stand in for what the provider would feed), otherwise `server`.\n * 4. Any other mockable server provider makes the recipe `mock`.\n * 5. Otherwise `live`.\n *\n * Contradictions fail the build: `runtime: 'browser'` on a `server-only`\n * package, or beside a required server provider with no browser option and no\n * mock.\n *\n * @packageDocumentation\n */\n\nimport type {\n PackageBrowserCapability,\n RecipeDefinition,\n RecipeDemo,\n RecipeDemoMode,\n RecipeProvider,\n} from '@happyvertical/smrt-types';\n\nexport type {\n PackageBrowserCapability,\n RecipeDemo,\n RecipeDemoMode,\n} from '@happyvertical/smrt-types';\n\n/** Modes from best to worst for a demo; used to pick the worst of several. */\nexport const RECIPE_DEMO_MODES: readonly RecipeDemoMode[] = [\n 'live',\n 'mock',\n 'sample',\n 'server',\n];\n\n/** The provider option that names a fake a demo can use instead of the real one. */\nexport const MOCK_PROVIDER_OPTION = 'mock';\n\nconst rank = (mode: RecipeDemoMode): number => RECIPE_DEMO_MODES.indexOf(mode);\n\n/** True when the provider holds credentials a browser cannot (`secrets` listed). */\nexport function isServerProvider(provider: RecipeProvider): boolean {\n return (provider.secrets?.length ?? 0) > 0;\n}\n\n/** True when one of the provider's options runs in a browser (`browserOptions`). */\nexport function hasBrowserOption(provider: RecipeProvider): boolean {\n return (provider.browserOptions?.length ?? 0) > 0;\n}\n\n/** True when a demo can fake the provider (`options` includes `mock`). */\nexport function isMockableProvider(provider: RecipeProvider): boolean {\n return provider.options.includes(MOCK_PROVIDER_OPTION);\n}\n\n/** What {@link deriveRecipeDemo} found: the classification and any contradictions. */\nexport interface RecipeDemoDerivation {\n /** Omitted when nothing is known about the package and the recipe declares nothing relevant. */\n demo?: RecipeDemo;\n /** One sentence per contradictory declaration; the build fails on any. */\n problems: string[];\n}\n\nfunction describePackage(capability: PackageBrowserCapability): string {\n const issues = capability.issues?.length\n ? ` (${capability.issues.join(', ')})`\n : '';\n const own = capability.reason\n ? `its package does not build for a browser: ${capability.reason}`\n : undefined;\n const inherited = capability.via?.length\n ? `its package depends on ${capability.via.join(', ')}, which do not build for a browser`\n : undefined;\n return `${[own, inherited].filter(Boolean).join('; and ') || 'its package does not build for a browser'}${issues}`;\n}\n\n/**\n * Classify one recipe. `capability` is the bundle-gate result for the recipe's\n * package, or `undefined` when the package is not measured (a consumer app's\n * own recipes); then only the recipe's own declarations decide.\n */\nexport function deriveRecipeDemo(\n recipe: Pick<RecipeDefinition, 'id' | 'runtime' | 'providers' | 'demoSeed'>,\n capability?: PackageBrowserCapability,\n): RecipeDemoDerivation {\n const problems: string[] = [];\n const reasons: string[] = [];\n let mode: RecipeDemoMode = 'live';\n const raise = (next: RecipeDemoMode) => {\n if (rank(next) > rank(mode)) mode = next;\n };\n\n const providers = recipe.providers ?? [];\n // A provider with an in-browser option is satisfied there: it needs no faking.\n const serverProviders = providers.filter(\n (provider) => isServerProvider(provider) && !hasBrowserOption(provider),\n );\n const inBrowser = providers.filter(\n (provider) => isServerProvider(provider) && hasBrowserOption(provider),\n );\n const mockable = serverProviders.filter(isMockableProvider);\n const blocking = serverProviders.filter(\n (provider) => provider.required && !isMockableProvider(provider),\n );\n const optionalUnavailable = serverProviders.filter(\n (provider) => !provider.required && !isMockableProvider(provider),\n );\n const packageServerOnly = capability?.status === 'server-only';\n\n if (recipe.runtime === 'browser') {\n if (capability && packageServerOnly) {\n problems.push(\n `recipe ${recipe.id}: runtime is \"browser\" but ${describePackage(capability)}; declare \"both\" or fix the package`,\n );\n }\n for (const provider of blocking) {\n problems.push(\n `recipe ${recipe.id}: runtime is \"browser\" but provider \"${provider.id}\" is required and holds secrets (${provider.secrets?.join(', ')}) a browser cannot keep; give it a browserOptions entry or a \"${MOCK_PROVIDER_OPTION}\" option, make it optional, or declare \"both\" or \"server\"`,\n );\n }\n }\n\n if (recipe.runtime === 'server') {\n raise('server');\n reasons.push('The recipe declares runtime \"server\".');\n }\n if (capability && packageServerOnly) {\n raise('server');\n reasons.push(`Needs a server because ${describePackage(capability)}.`);\n }\n if (blocking.length > 0) {\n const names = blocking.map((provider) => provider.id).join(', ');\n if (recipe.demoSeed) {\n raise('sample');\n reasons.push(\n `Required provider ${names} holds secrets a browser cannot keep and has no in-browser or \"${MOCK_PROVIDER_OPTION}\" option; the demo seed stands in for its data.`,\n );\n } else {\n raise('server');\n reasons.push(\n `Required provider ${names} holds secrets a browser cannot keep and has no in-browser or \"${MOCK_PROVIDER_OPTION}\" option and the recipe has no demo seed.`,\n );\n }\n }\n if (mockable.length > 0) {\n raise('mock');\n reasons.push(\n `Provider ${mockable.map((provider) => provider.id).join(', ')} is faked in a demo.`,\n );\n }\n if (inBrowser.length > 0) {\n reasons.push(\n `Provider ${inBrowser.map((provider) => provider.id).join(', ')} has an in-browser option (${inBrowser\n .flatMap((provider) => provider.browserOptions ?? [])\n .join(', ')}).`,\n );\n }\n if (optionalUnavailable.length > 0) {\n reasons.push(\n `Optional provider ${optionalUnavailable.map((provider) => provider.id).join(', ')} is unavailable in a browser.`,\n );\n }\n\n const declares =\n recipe.runtime !== undefined ||\n providers.length > 0 ||\n recipe.demoSeed !== undefined;\n if (!capability && !declares) return { problems };\n if (!capability) {\n reasons.push('The package browser capability is not measured.');\n }\n if (mode === 'live' && capability && reasons.length === 0) {\n reasons.push('The package builds for a browser and nothing needs faking.');\n }\n\n const demo: RecipeDemo = {\n mode,\n reasons,\n ...(mockable.length > 0\n ? { mocked: mockable.map((provider) => provider.id) }\n : {}),\n };\n return { demo, problems };\n}\n\n/**\n * The mode a host should show for `recipeId`: the worst of its own `demo` and\n * that of every recipe it needs. Each `requiresAny` list counts its best known\n * alternative. Ids that are unknown, or whose recipe has no `demo`, are\n * ignored, so a partial catalog degrades to the recipe's own answer. Returns\n * `undefined` when the recipe is unknown or has no `demo` of its own.\n */\nexport function effectiveRecipeDemo(\n recipeId: string,\n recipes: readonly Pick<\n RecipeDefinition,\n 'id' | 'requires' | 'requiresAny' | 'demo'\n >[],\n): RecipeDemo | undefined {\n const byId = new Map(recipes.map((recipe) => [recipe.id, recipe]));\n\n const resolve = (\n id: string,\n trail: readonly string[],\n ): RecipeDemo | undefined => {\n const recipe = byId.get(id);\n if (!recipe?.demo || trail.includes(id)) return recipe?.demo;\n let mode = recipe.demo.mode;\n const reasons = [...recipe.demo.reasons];\n const consider = (needed: string, via: RecipeDemo) => {\n if (rank(via.mode) > rank(mode)) mode = via.mode;\n if (via.mode !== 'live') {\n reasons.push(`Needs ${needed}, which is ${via.mode}.`);\n }\n };\n for (const needed of recipe.requires) {\n const demo = resolve(needed, [...trail, id]);\n if (demo) consider(needed, demo);\n }\n for (const alternatives of recipe.requiresAny ?? []) {\n const known = alternatives\n .map((needed) => ({ needed, demo: resolve(needed, [...trail, id]) }))\n .filter(\n (entry): entry is { needed: string; demo: RecipeDemo } =>\n entry.demo !== undefined,\n );\n if (known.length === 0) continue;\n const best = known.reduce((a, b) =>\n rank(b.demo.mode) < rank(a.demo.mode) ? b : a,\n );\n consider(best.needed, best.demo);\n }\n return {\n mode,\n reasons,\n ...(recipe.demo.mocked ? { mocked: recipe.demo.mocked } : {}),\n };\n };\n\n return resolve(recipeId, []);\n}\n"],"mappings":"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"}
package/dist/recipe.d.ts CHANGED
@@ -23,6 +23,7 @@
23
23
  * static models = [Order];
24
24
  * static nav = [{ label: 'Sales Orders', model: Order }];
25
25
  * static requires = ['commerce.customers'];
26
+ * static section = { id: 'sales', label: 'Sales', icon: 'shoppingBag' };
26
27
  * static options = {
27
28
  * Order: { fields: { status: { default: 'draft', locked: true } } },
28
29
  * };
@@ -30,7 +31,7 @@
30
31
  * }
31
32
  * ```
32
33
  */
33
- import type { RecipeExposureNarrowing, RecipeFieldOptions } from '@happyvertical/smrt-types';
34
+ import type { RecipeDemoSeed, RecipeExposureNarrowing, RecipeFieldOptions, RecipeProvider, RecipeRuntime, RecipeSurface } from '@happyvertical/smrt-types';
34
35
  /**
35
36
  * A model class as a recipe references it. Any constructor qualifies; the
36
37
  * parameter list is `never[]` so models with required constructor options
@@ -41,6 +42,38 @@ export type SmrtRecipeModel = abstract new (...args: never[]) => object;
41
42
  export interface SmrtRecipeNavEntry {
42
43
  label: string;
43
44
  model: SmrtRecipeModel;
45
+ /** A shell icon name (`users`, `calendar`...) drawn on the entry's row. */
46
+ icon?: string;
47
+ /** One friendly line for a business owner, shown on the section card. */
48
+ description?: string;
49
+ /**
50
+ * Fixed key making the entry's layout id `item:<pkg>:<Model>:<key>`. Needed
51
+ * for a second entry over the same model, and for a `filter`.
52
+ */
53
+ key?: string;
54
+ /** What "New" creates when the label is not a countable noun. */
55
+ noun?: string;
56
+ /**
57
+ * Narrow the entry to rows whose `field` equals `value`; new rows from the
58
+ * view carry the value. Needs a `key`.
59
+ */
60
+ filter?: {
61
+ field: string;
62
+ value: string;
63
+ };
64
+ }
65
+ /** Recipes sharing a `group.id` share one card in a host. */
66
+ export interface SmrtRecipeGroup {
67
+ id: string;
68
+ label: string;
69
+ summary?: string;
70
+ }
71
+ /** The navigation section a recipe suggests; same `id` shares a section. */
72
+ export interface SmrtRecipeSection {
73
+ id: string;
74
+ label: string;
75
+ icon?: string;
76
+ description?: string;
44
77
  }
45
78
  /**
46
79
  * Curation hints for one model, keyed in `static options` by the model's class
@@ -70,6 +103,22 @@ export declare abstract class SmrtRecipe {
70
103
  static nav: readonly SmrtRecipeNavEntry[];
71
104
  /** Ids of recipes needed first; they may live in other packages. */
72
105
  static requires: readonly string[];
106
+ /**
107
+ * Alternatives: at least one id of each inner list must be on. Adding the
108
+ * recipe with none on adds the first of the list.
109
+ */
110
+ static requiresAny: readonly (readonly string[])[];
111
+ /**
112
+ * Card the recipe is a sub-switch of in a host such as an app shell. Recipes
113
+ * with the same `group.id` share it; the first declaration supplies the
114
+ * card's `label` and `summary`.
115
+ */
116
+ static group?: SmrtRecipeGroup;
117
+ /**
118
+ * Navigation section the recipe suggests its `nav` entries sit under. The
119
+ * host owns sections and the user may rename them, so keep `id` stable.
120
+ */
121
+ static section?: SmrtRecipeSection;
73
122
  /** Curation hints keyed by model class name. */
74
123
  static options: Readonly<Record<string, SmrtRecipeModelOptions>>;
75
124
  /**
@@ -79,5 +128,19 @@ export declare abstract class SmrtRecipe {
79
128
  * `renderHelp` for how a host follows the app's options.
80
129
  */
81
130
  static help?: string;
131
+ /**
132
+ * Non-model surfaces (#3708): shell widgets, routes, settings panels,
133
+ * playground entries and overview `widget`s (#3727, with an options schema,
134
+ * data needs and allowed overview ids). Components are referenced by
135
+ * `'<module specifier>#<ExportName>'`, never imported by the manifest.
136
+ * Write the array `as const` so literal kinds and slots survive.
137
+ */
138
+ static surfaces?: readonly RecipeSurface[];
139
+ /** Providers and secrets the recipe needs or can use (#3708). */
140
+ static providers?: readonly RecipeProvider[];
141
+ /** Where the recipe's runtime pieces can run; omitted means `both`. */
142
+ static runtime?: RecipeRuntime;
143
+ /** Demo fixture data: a fixture export or small inline JSON (#3708). */
144
+ static demoSeed?: RecipeDemoSeed;
82
145
  }
83
146
  //# sourceMappingURL=recipe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"recipe.d.ts","sourceRoot":"","sources":["../src/recipe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EACnB,MAAM,2BAA2B,CAAC;AAEnC;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,MAAM,CAAC;AAExE,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE;QACT,GAAG,CAAC,EAAE,uBAAuB,CAAC;QAC9B,GAAG,CAAC,EAAE,uBAAuB,CAAC;QAC9B,GAAG,CAAC,EAAE,uBAAuB,CAAC;KAC/B,CAAC;CACH;AAED,8BAAsB,UAAU;IAC9B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC;IACvB,mDAAmD;IACnD,MAAM,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAM;IACxC,yEAAyE;IACzE,MAAM,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC1C,sEAAsE;IACtE,MAAM,CAAC,GAAG,EAAE,SAAS,kBAAkB,EAAE,CAAM;IAC/C,oEAAoE;IACpE,MAAM,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAM;IACxC,gDAAgD;IAChD,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAM;IACtE;;;;;OAKG;IACH,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"recipe.d.ts","sourceRoot":"","sources":["../src/recipe.ts"],"names":[],"mappings":"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"}
package/dist/recipe.js CHANGED
@@ -14,6 +14,22 @@ var SmrtRecipe = class {
14
14
  static nav = [];
15
15
  /** Ids of recipes needed first; they may live in other packages. */
16
16
  static requires = [];
17
+ /**
18
+ * Alternatives: at least one id of each inner list must be on. Adding the
19
+ * recipe with none on adds the first of the list.
20
+ */
21
+ static requiresAny = [];
22
+ /**
23
+ * Card the recipe is a sub-switch of in a host such as an app shell. Recipes
24
+ * with the same `group.id` share it; the first declaration supplies the
25
+ * card's `label` and `summary`.
26
+ */
27
+ static group;
28
+ /**
29
+ * Navigation section the recipe suggests its `nav` entries sit under. The
30
+ * host owns sections and the user may rename them, so keep `id` stable.
31
+ */
32
+ static section;
17
33
  /** Curation hints keyed by model class name. */
18
34
  static options = {};
19
35
  /**
@@ -23,6 +39,20 @@ var SmrtRecipe = class {
23
39
  * `renderHelp` for how a host follows the app's options.
24
40
  */
25
41
  static help;
42
+ /**
43
+ * Non-model surfaces (#3708): shell widgets, routes, settings panels,
44
+ * playground entries and overview `widget`s (#3727, with an options schema,
45
+ * data needs and allowed overview ids). Components are referenced by
46
+ * `'<module specifier>#<ExportName>'`, never imported by the manifest.
47
+ * Write the array `as const` so literal kinds and slots survive.
48
+ */
49
+ static surfaces;
50
+ /** Providers and secrets the recipe needs or can use (#3708). */
51
+ static providers;
52
+ /** Where the recipe's runtime pieces can run; omitted means `both`. */
53
+ static runtime;
54
+ /** Demo fixture data: a fixture export or small inline JSON (#3708). */
55
+ static demoSeed;
26
56
  };
27
57
  //#endregion
28
58
  export { SmrtRecipe };
@@ -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 options = {\n * Order: { fields: { status: { default: 'draft', locked: true } } },\n * };\n * static help = './sales.recipe.md';\n * }\n * ```\n */\n\nimport type {\n RecipeExposureNarrowing,\n RecipeFieldOptions,\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}\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 /** 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"],"mappings":";AAiEA,IAAsB,aAAtB,MAAiC;;CAE/B,OAAO;;CAEP,OAAO;;CAEP,OAAO;;CAEP,OAAO,WAA8B,CAAC;;CAEtC,OAAO;;CAEP,OAAO,MAAqC,CAAC;;CAE7C,OAAO,WAA8B,CAAC;;CAEtC,OAAO,UAA4D,CAAC;;;;;;;CAOpE,OAAO;AACT"}
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":";AAqGA,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;;CAEP,OAAO;;CAEP,OAAO;AACT"}
@@ -4,9 +4,9 @@ import { declareChangeFeedSensitiveTable, setChangeFeedSensitiveClassResolver }
4
4
  import { tableNameFromClass } from "../utils.js";
5
5
  import { bumpRegistryGeneration } from "./generation.js";
6
6
  import { createQualifiedName, isQualifiedName, parseQualifiedName } from "../utils/qualified-names.js";
7
- import { getClasses, getCollections, getConstructorFieldDecorators, getConstructorIndex, getConstructorTenantScopedDeclarations, getInheritanceCache, getLegacyFieldDecorators, getSourceFileFromStack, getStampedPackageName, getStiSiblingsLoaded, verboseLog } from "./shared-state.js";
8
7
  import { cloneManifestSchemaColumns, findWorkspaceRootSync, getLocalTestManifestCache, getManifestCache, getNodeBuiltins, getStaticManifestCache, getTestManifestCache, packageStampsConstructors } from "../manifest/store.js";
9
8
  import { discoverManifestSync, discoverSTISiblingsSync, getPackageName, lookupInManifest, readProjectManifestSync } from "../manifest/manifest-loader.js";
9
+ import { getClasses, getCollections, getConstructorFieldDecorators, getConstructorIndex, getConstructorTenantScopedDeclarations, getInheritanceCache, getLegacyFieldDecorators, getSourceFileFromStack, getStampedPackageName, getStiSiblingsLoaded, verboseLog } from "./shared-state.js";
10
10
  import { assertQualifiedNameAliasesAvailable } from "./qualified-name-aliases.js";
11
11
  import { findClass, findClassesByName, getCanonicalClassName, hasClassCaseInsensitive, qualifyExtendsName } from "./name-resolver.js";
12
12
  import { buildInheritanceChain } from "./inheritance-resolver.js";
@@ -887,7 +887,7 @@ function registerUntracked(ctor, config = {}) {
887
887
  validators,
888
888
  validationRules,
889
889
  tools: manifestEntry?.tools,
890
- collection: config.collection ?? manifestEntry?.collection ?? inheritedStiCollection(ctor) ?? pluralizeCollection(name),
890
+ collection: mergedConfig.collection ?? manifestEntry?.collection ?? inheritedStiCollection(ctor) ?? pluralizeCollection(name),
891
891
  collectionConstructor: promotedCollectionConstructor,
892
892
  replacedQualifiedNames: promotedReplacementAncestry,
893
893
  packageName,