@happyvertical/smrt-svelte 0.55.10 → 0.55.11

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 (55) hide show
  1. package/AGENTS.md +1 -0
  2. package/agents/cookbook-shell.md +176 -0
  3. package/agents/overview-presentation.md +8 -0
  4. package/agents/overview-surfaces.md +11 -0
  5. package/agents/screens.md +5 -0
  6. package/dist/components/cookbook/CookbookApp.svelte +236 -0
  7. package/dist/components/cookbook/CookbookApp.svelte.d.ts +5 -0
  8. package/dist/components/cookbook/CookbookApp.svelte.d.ts.map +1 -0
  9. package/dist/components/cookbook/CookbookSectionOverview.svelte +71 -0
  10. package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts +20 -0
  11. package/dist/components/cookbook/CookbookSectionOverview.svelte.d.ts.map +1 -0
  12. package/dist/components/cookbook/CookbookSectionPage.svelte +133 -0
  13. package/dist/components/cookbook/CookbookSectionPage.svelte.d.ts +5 -0
  14. package/dist/components/cookbook/CookbookSectionPage.svelte.d.ts.map +1 -0
  15. package/dist/components/cookbook/brand-theme.d.ts +11 -0
  16. package/dist/components/cookbook/brand-theme.d.ts.map +1 -0
  17. package/dist/components/cookbook/brand-theme.js +24 -0
  18. package/dist/components/cookbook/build.d.ts +8 -0
  19. package/dist/components/cookbook/build.d.ts.map +1 -0
  20. package/dist/components/cookbook/build.js +277 -0
  21. package/dist/components/cookbook/catalog.d.ts +37 -0
  22. package/dist/components/cookbook/catalog.d.ts.map +1 -0
  23. package/dist/components/cookbook/catalog.js +99 -0
  24. package/dist/components/cookbook/context.d.ts +23 -0
  25. package/dist/components/cookbook/context.d.ts.map +1 -0
  26. package/dist/components/cookbook/context.js +15 -0
  27. package/dist/components/cookbook/index.d.ts +22 -0
  28. package/dist/components/cookbook/index.d.ts.map +1 -0
  29. package/dist/components/cookbook/index.js +20 -0
  30. package/dist/components/cookbook/page.d.ts +23 -0
  31. package/dist/components/cookbook/page.d.ts.map +1 -0
  32. package/dist/components/cookbook/page.js +32 -0
  33. package/dist/components/cookbook/registry.d.ts +37 -0
  34. package/dist/components/cookbook/registry.d.ts.map +1 -0
  35. package/dist/components/cookbook/registry.js +38 -0
  36. package/dist/components/cookbook/sections.d.ts +60 -0
  37. package/dist/components/cookbook/sections.d.ts.map +1 -0
  38. package/dist/components/cookbook/sections.js +88 -0
  39. package/dist/components/cookbook/server.d.ts +61 -0
  40. package/dist/components/cookbook/server.d.ts.map +1 -0
  41. package/dist/components/cookbook/server.js +108 -0
  42. package/dist/components/cookbook/theme.d.ts +21 -0
  43. package/dist/components/cookbook/theme.d.ts.map +1 -0
  44. package/dist/components/cookbook/theme.js +53 -0
  45. package/dist/components/cookbook/types.d.ts +204 -0
  46. package/dist/components/cookbook/types.d.ts.map +1 -0
  47. package/dist/components/cookbook/types.js +1 -0
  48. package/dist/components/overview/OverviewGrid.svelte +5 -2
  49. package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -1
  50. package/dist/components/overview/grid-types.d.ts +6 -2
  51. package/dist/components/overview/grid-types.d.ts.map +1 -1
  52. package/dist/i18n/strings.overview.d.ts +3 -0
  53. package/dist/i18n/strings.overview.d.ts.map +1 -1
  54. package/dist/i18n/strings.overview.js +4 -0
  55. package/package.json +25 -14
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @happyvertical/smrt-svelte/cookbook
3
+ *
4
+ * A cookbook-driven app shell (#3749): `buildCookbookShell` turns a cookbook
5
+ * plus a recipe catalog into `AppShell` configuration (sections, entries,
6
+ * layout, theme, section overviews, recipe surfaces); `CookbookApp` renders it
7
+ * and `CookbookSectionPage` is a section's own page. Browser-safe: use
8
+ * `@happyvertical/smrt-svelte/cookbook/server` to load the cookbook and the
9
+ * installed packages' manifests in a `+layout.server.ts`.
10
+ */
11
+ export { applyCookbookTheme } from './brand-theme.js';
12
+ export { buildCookbookShell, humanize, pluralize } from './build.js';
13
+ export { default as CookbookApp } from './CookbookApp.svelte';
14
+ export { default as CookbookSectionPage } from './CookbookSectionPage.svelte';
15
+ export { type CookbookManifest, catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
16
+ export { type CookbookContext, getCookbookContext, tryGetCookbookContext, } from './context.js';
17
+ export { type CookbookOverviewStore, createSectionOverview, loadSectionWidget, } from './page.js';
18
+ export { type CookbookRegistry, type CookbookRegistryOptions, type CookbookWidgetContext, createCookbookRegistry, loadShortcuts, } from './registry.js';
19
+ export { buildNavSections, DEFAULT_ENTRY_ICON, DEFAULT_SECTION_ICON, defaultEntryPath, defaultSectionPath, FEATURE_SECTION, navItemId, navSectionOf, sectionIdFromSlug, sectionLayoutId, sectionSlug, } from './sections.js';
20
+ export { brandThemeId, DEFAULT_COLOR_SCHEME, DEFAULT_THEME_PRESET, normalizeHex, resolveCookbookTheme, THEME_FONTS, } from './theme.js';
21
+ export type * from './types.js';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EACL,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,qBAAqB,EAC1B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,sBAAsB,EACtB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,WAAW,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,EACpB,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,mBAAmB,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @happyvertical/smrt-svelte/cookbook
3
+ *
4
+ * A cookbook-driven app shell (#3749): `buildCookbookShell` turns a cookbook
5
+ * plus a recipe catalog into `AppShell` configuration (sections, entries,
6
+ * layout, theme, section overviews, recipe surfaces); `CookbookApp` renders it
7
+ * and `CookbookSectionPage` is a section's own page. Browser-safe: use
8
+ * `@happyvertical/smrt-svelte/cookbook/server` to load the cookbook and the
9
+ * installed packages' manifests in a `+layout.server.ts`.
10
+ */
11
+ export { applyCookbookTheme } from './brand-theme.js';
12
+ export { buildCookbookShell, humanize, pluralize } from './build.js';
13
+ export { default as CookbookApp } from './CookbookApp.svelte';
14
+ export { default as CookbookSectionPage } from './CookbookSectionPage.svelte';
15
+ export { catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
16
+ export { getCookbookContext, tryGetCookbookContext, } from './context.js';
17
+ export { createSectionOverview, loadSectionWidget, } from './page.js';
18
+ export { createCookbookRegistry, loadShortcuts, } from './registry.js';
19
+ export { buildNavSections, DEFAULT_ENTRY_ICON, DEFAULT_SECTION_ICON, defaultEntryPath, defaultSectionPath, FEATURE_SECTION, navItemId, navSectionOf, sectionIdFromSlug, sectionLayoutId, sectionSlug, } from './sections.js';
20
+ export { brandThemeId, DEFAULT_COLOR_SCHEME, DEFAULT_THEME_PRESET, normalizeHex, resolveCookbookTheme, THEME_FONTS, } from './theme.js';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * How a section page wires its overview to the cookbook (#3749): the
3
+ * controller reads the page's stored override and writes every edit back, so
4
+ * the cookbook is the single source of truth. Ported from smrt-planner's
5
+ * `overviews/page.ts`.
6
+ */
7
+ import { type OverviewController } from '../overview/controller.svelte.js';
8
+ import type { WidgetRegistry } from '../overview/registry.js';
9
+ import type { OverviewDefinition, OverviewOverride, OverviewWidget } from '../overview/types.js';
10
+ /** The part of the app an overview reads and writes. */
11
+ export interface CookbookOverviewStore {
12
+ overview(id: string): unknown;
13
+ setOverview(id: string, override: OverviewOverride | null): void;
14
+ }
15
+ /**
16
+ * One widget's data through the same sanitize-and-load path (timeout,
17
+ * serializable check) as a page load. A failed load throws, which the grid
18
+ * shows as that widget's error tile.
19
+ */
20
+ export declare function loadSectionWidget(definition: OverviewDefinition, registry: WidgetRegistry, context: Record<string, unknown>, widget: OverviewWidget, signal?: AbortSignal): Promise<unknown>;
21
+ /** The controller of a section's overview, persisted through `store`. */
22
+ export declare function createSectionOverview(definition: OverviewDefinition, registry: WidgetRegistry, store: CookbookOverviewStore, context: () => Record<string, unknown>): OverviewController;
23
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/page.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACf,MAAM,sBAAsB,CAAC;AAE9B,wDAAwD;AACxD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;CAClE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,kBAAkB,EAC9B,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAYlB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,kBAAkB,EAC9B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrC,kBAAkB,CAUpB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * How a section page wires its overview to the cookbook (#3749): the
3
+ * controller reads the page's stored override and writes every edit back, so
4
+ * the cookbook is the single source of truth. Ported from smrt-planner's
5
+ * `overviews/page.ts`.
6
+ */
7
+ import { createOverview, } from '../overview/controller.svelte.js';
8
+ import { loadOverview } from '../overview/load.js';
9
+ /**
10
+ * One widget's data through the same sanitize-and-load path (timeout,
11
+ * serializable check) as a page load. A failed load throws, which the grid
12
+ * shows as that widget's error tile.
13
+ */
14
+ export async function loadSectionWidget(definition, registry, context, widget, signal) {
15
+ const loaded = await loadOverview({ widgets: [widget] }, definition, registry, { ...context, signal, locale: 'en-US' });
16
+ const result = loaded.widgets[0];
17
+ if (!result || result.status === 'error') {
18
+ throw new Error(result?.error?.code ?? 'load_failed');
19
+ }
20
+ return result.data;
21
+ }
22
+ /** The controller of a section's overview, persisted through `store`. */
23
+ export function createSectionOverview(definition, registry, store, context) {
24
+ const id = definition.id;
25
+ return createOverview({
26
+ definition,
27
+ registry,
28
+ override: () => store.overview(id),
29
+ onchange: (override) => store.setOverview(id, override),
30
+ loadWidget: (widget, signal) => loadSectionWidget(definition, registry, context(), widget, signal),
31
+ });
32
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The widget registry of a cookbook app's section pages (#3749): the core
3
+ * widgets (a shortcuts loader that reads the shell's resolved sections, plus
4
+ * whatever data loaders the host injects) and the recipes' `widget` surfaces.
5
+ */
6
+ import { type RecipeExportResolver, type RecipeWidgetRegistration, type RecipeWidgetSource } from '../overview/recipe-widgets.js';
7
+ import { type WidgetRegistry } from '../overview/registry.js';
8
+ import type { WidgetLoadContext } from '../overview/types.js';
9
+ import { type CoreWidgetLoaders } from '../overview/widgets/core.js';
10
+ import { type ShortcutNavSection, type ShortcutsWidgetData } from '../overview/widgets/data.js';
11
+ /** What a section page hands its widgets' loaders besides the host's own context. */
12
+ export interface CookbookWidgetContext {
13
+ /** The shell's resolved sections (renames, hides and moves applied). */
14
+ sections: readonly ShortcutNavSection[];
15
+ }
16
+ /**
17
+ * `shortcuts`: the section's menu entries as cards, as the shell resolves
18
+ * them. `section` names another section by its id without the `section:` /
19
+ * `custom:` prefix; absent, the overview's own section.
20
+ */
21
+ export declare function loadShortcuts(options: Record<string, unknown>, ctx: WidgetLoadContext): ShortcutsWidgetData;
22
+ export interface CookbookRegistryOptions {
23
+ /** Data loaders for `metric`, `records` and `chart` (omitted ones are not registered). */
24
+ loaders?: Omit<CoreWidgetLoaders, 'shortcuts'>;
25
+ /** Recipes whose `widget` surfaces to register. */
26
+ widgets?: readonly RecipeWidgetSource[];
27
+ /** The host's module resolver for recipe exports. */
28
+ resolveExport?: RecipeExportResolver;
29
+ }
30
+ export interface CookbookRegistry {
31
+ registry: WidgetRegistry;
32
+ /** Recipe widgets registered or skipped, for diagnostics. */
33
+ recipeWidgets: RecipeWidgetRegistration | null;
34
+ }
35
+ /** An isolated registry for one cookbook app. */
36
+ export declare function createCookbookRegistry(options?: CookbookRegistryOptions): Promise<CookbookRegistry>;
37
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EAExB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,6BAA6B,CAAC;AAErC,qFAAqF;AACrF,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,EAAE,iBAAiB,GACrB,mBAAmB,CAYrB;AAED,MAAM,WAAW,uBAAuB;IACtC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;IAC/C,mDAAmD;IACnD,OAAO,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACxC,qDAAqD;IACrD,aAAa,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,6DAA6D;IAC7D,aAAa,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAChD;AAED,iDAAiD;AACjD,wBAAsB,sBAAsB,CAC1C,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAe3B"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The widget registry of a cookbook app's section pages (#3749): the core
3
+ * widgets (a shortcuts loader that reads the shell's resolved sections, plus
4
+ * whatever data loaders the host injects) and the recipes' `widget` surfaces.
5
+ */
6
+ import { registerRecipeWidgets, } from '../overview/recipe-widgets.js';
7
+ import { createWidgetRegistry, } from '../overview/registry.js';
8
+ import { registerCoreWidgets, } from '../overview/widgets/core.js';
9
+ import { shortcutsFromNav, } from '../overview/widgets/data.js';
10
+ /**
11
+ * `shortcuts`: the section's menu entries as cards, as the shell resolves
12
+ * them. `section` names another section by its id without the `section:` /
13
+ * `custom:` prefix; absent, the overview's own section.
14
+ */
15
+ export function loadShortcuts(options, ctx) {
16
+ const { sections } = ctx;
17
+ if (!sections)
18
+ throw new Error('the cookbook widget context is missing');
19
+ const named = typeof options.section === 'string' ? options.section : '';
20
+ const sectionId = named
21
+ ? sections.find((s) => s.id === `section:${named}` || s.id === `custom:${named}`)?.id
22
+ : ctx.overviewId;
23
+ if (!sectionId)
24
+ throw new Error(`unknown section ${named}`);
25
+ return shortcutsFromNav(sections, sectionId);
26
+ }
27
+ /** An isolated registry for one cookbook app. */
28
+ export async function createCookbookRegistry(options = {}) {
29
+ const registry = createWidgetRegistry();
30
+ registerCoreWidgets(registry, {
31
+ ...options.loaders,
32
+ shortcuts: loadShortcuts,
33
+ });
34
+ const recipeWidgets = options.widgets && options.widgets.length > 0 && options.resolveExport
35
+ ? await registerRecipeWidgets(registry, options.widgets, options.resolveExport)
36
+ : null;
37
+ return { registry, recipeWidgets };
38
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Navigation sections and entry ids (#3749), ported from smrt-planner's
3
+ * `recipes/sections.ts` so a generated app and the planner derive the same
4
+ * ids: a saved layout names them, so they must stay stable.
5
+ */
6
+ import type { RecipeSection } from '@happyvertical/smrt-types';
7
+ import type { CookbookRecipe } from './types.js';
8
+ /** The section that holds feature models no recipe covers. */
9
+ export declare const FEATURE_SECTION: RecipeSection;
10
+ /** Icon of an entry whose recipe names none, and of feature entries. */
11
+ export declare const DEFAULT_ENTRY_ICON = "fileText";
12
+ /** The icon of a section the user made, or one the host knows nothing of. */
13
+ export declare const DEFAULT_SECTION_ICON = "folder";
14
+ /** The id of the group (or recipe) a recipe belongs to. */
15
+ export declare function groupId(recipe: Pick<CookbookRecipe, 'id' | 'group'>): string;
16
+ /**
17
+ * The nav section a recipe's entries sit under by default: the section it
18
+ * suggests, else its group, else the recipe itself.
19
+ */
20
+ export declare function navSectionOf(recipe: CookbookRecipe): RecipeSection;
21
+ export type NavSectionGroup = RecipeSection & {
22
+ recipes: CookbookRecipe[];
23
+ };
24
+ /** Nav sections of the given recipes, in the order their first recipe comes. */
25
+ export declare function buildNavSections(recipes: readonly CookbookRecipe[]): NavSectionGroup[];
26
+ /**
27
+ * The stable layout id of a navigation item: `item:<package>:<Model>`, or
28
+ * `item:<package>:<Model>:<key>` when a model appears twice in the nav and the
29
+ * extra entry declares a fixed `key`. It never names the section, the label or
30
+ * the href, so renaming an entry or moving a recipe's section leaves a saved
31
+ * layout valid.
32
+ */
33
+ export declare function navItemId(packageId: string, modelName: string, key?: string): string;
34
+ /** `section:<id>` for a recipe-suggested section. */
35
+ export declare function sectionLayoutId(id: string): string;
36
+ /**
37
+ * A section page lives at `/s/<slug>/`. The slug is the layout id with its
38
+ * `kind:` prefix turned into `kind-` (`section:sales` -> `section-sales`), so
39
+ * no path segment holds a colon. Only the first colon is the prefix.
40
+ */
41
+ export declare function sectionSlug(id: string): string;
42
+ /** The layout id a slug names; the inverse of {@link sectionSlug}. */
43
+ export declare function sectionIdFromSlug(slug: string): string;
44
+ /** Default in-app path of a section page. */
45
+ export declare function defaultSectionPath(id: string): string;
46
+ /** Default in-app path of a model entry: `/m/<pkg>/<Model>/[<key>/]`. */
47
+ export declare function defaultEntryPath(entry: {
48
+ packageId: string;
49
+ modelName: string;
50
+ key?: string;
51
+ }): string;
52
+ /**
53
+ * The line under an entry on its section card: the recipe's own text first,
54
+ * then the model's description, then a plain fallback.
55
+ */
56
+ export declare function entryDescription(own: string | undefined, model: {
57
+ description?: string;
58
+ name: string;
59
+ }): string;
60
+ //# sourceMappingURL=sections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/sections.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,8DAA8D;AAC9D,eAAO,MAAM,eAAe,EAAE,aAK7B,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,aAAa,CAAC;AAE7C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C,2DAA2D;AAC3D,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,MAAM,CAE5E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,aAAa,CAOlE;AAED,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG;IAAE,OAAO,EAAE,cAAc,EAAE,CAAA;CAAE,CAAC;AAE5E,gFAAgF;AAChF,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,cAAc,EAAE,GACjC,eAAe,EAAE,CAanB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,GACX,MAAM,CAER;AAED,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,6CAA6C;AAC7C,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAET;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,KAAK,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C,MAAM,CAMR"}
@@ -0,0 +1,88 @@
1
+ /** The section that holds feature models no recipe covers. */
2
+ export const FEATURE_SECTION = {
3
+ id: 'more',
4
+ label: 'More',
5
+ icon: 'layers',
6
+ description: 'Extra records you added one at a time.',
7
+ };
8
+ /** Icon of an entry whose recipe names none, and of feature entries. */
9
+ export const DEFAULT_ENTRY_ICON = 'fileText';
10
+ /** The icon of a section the user made, or one the host knows nothing of. */
11
+ export const DEFAULT_SECTION_ICON = 'folder';
12
+ /** The id of the group (or recipe) a recipe belongs to. */
13
+ export function groupId(recipe) {
14
+ return recipe.group?.id ?? recipe.id;
15
+ }
16
+ /**
17
+ * The nav section a recipe's entries sit under by default: the section it
18
+ * suggests, else its group, else the recipe itself.
19
+ */
20
+ export function navSectionOf(recipe) {
21
+ return (recipe.section ?? {
22
+ id: groupId(recipe),
23
+ label: recipe.group?.label ?? recipe.label,
24
+ });
25
+ }
26
+ /** Nav sections of the given recipes, in the order their first recipe comes. */
27
+ export function buildNavSections(recipes) {
28
+ const out = new Map();
29
+ for (const recipe of recipes) {
30
+ const nav = navSectionOf(recipe);
31
+ const existing = out.get(nav.id);
32
+ if (existing) {
33
+ existing.recipes.push(recipe);
34
+ // The first recipe supplies the label; a later one may add what is missing.
35
+ existing.icon ??= nav.icon;
36
+ existing.description ??= nav.description;
37
+ }
38
+ else
39
+ out.set(nav.id, { ...nav, recipes: [recipe] });
40
+ }
41
+ return [...out.values()];
42
+ }
43
+ /**
44
+ * The stable layout id of a navigation item: `item:<package>:<Model>`, or
45
+ * `item:<package>:<Model>:<key>` when a model appears twice in the nav and the
46
+ * extra entry declares a fixed `key`. It never names the section, the label or
47
+ * the href, so renaming an entry or moving a recipe's section leaves a saved
48
+ * layout valid.
49
+ */
50
+ export function navItemId(packageId, modelName, key) {
51
+ return `item:${packageId}:${modelName}${key ? `:${key}` : ''}`;
52
+ }
53
+ /** `section:<id>` for a recipe-suggested section. */
54
+ export function sectionLayoutId(id) {
55
+ return `section:${id}`;
56
+ }
57
+ /**
58
+ * A section page lives at `/s/<slug>/`. The slug is the layout id with its
59
+ * `kind:` prefix turned into `kind-` (`section:sales` -> `section-sales`), so
60
+ * no path segment holds a colon. Only the first colon is the prefix.
61
+ */
62
+ export function sectionSlug(id) {
63
+ return id.replace(':', '-');
64
+ }
65
+ /** The layout id a slug names; the inverse of {@link sectionSlug}. */
66
+ export function sectionIdFromSlug(slug) {
67
+ return slug.replace('-', ':');
68
+ }
69
+ /** Default in-app path of a section page. */
70
+ export function defaultSectionPath(id) {
71
+ return `/s/${sectionSlug(id)}/`;
72
+ }
73
+ /** Default in-app path of a model entry: `/m/<pkg>/<Model>/[<key>/]`. */
74
+ export function defaultEntryPath(entry) {
75
+ return `/m/${entry.packageId}/${entry.modelName}/${entry.key ? `${entry.key}/` : ''}`;
76
+ }
77
+ function humanizeName(name) {
78
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase();
79
+ }
80
+ /**
81
+ * The line under an entry on its section card: the recipe's own text first,
82
+ * then the model's description, then a plain fallback.
83
+ */
84
+ export function entryDescription(own, model) {
85
+ return (own?.trim() ||
86
+ model.description?.trim() ||
87
+ `Your ${humanizeName(model.name)} records, all in one place.`);
88
+ }
@@ -0,0 +1,61 @@
1
+ import type { Cookbook } from '@happyvertical/smrt-types';
2
+ import { type CookbookManifest } from './catalog.js';
3
+ import type { CookbookCatalog } from './types.js';
4
+ export { buildCookbookShell } from './build.js';
5
+ export { type CookbookManifest, catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
6
+ export { resolveCookbookTheme } from './theme.js';
7
+ export type * from './types.js';
8
+ export declare const COOKBOOK_FILE = "smrt.cookbook.json";
9
+ /** The result of validating a cookbook document (`validateCookbook` in smrt-core). */
10
+ export type CookbookValidation = {
11
+ ok: true;
12
+ cookbook: Cookbook;
13
+ warnings?: string[];
14
+ } | {
15
+ ok: false;
16
+ errors: string[];
17
+ };
18
+ export type CookbookValidator = (document: unknown, options?: {
19
+ recipes?: Iterable<string>;
20
+ }) => CookbookValidation;
21
+ /** What `+layout.server.ts` returns and `CookbookApp` takes. Plain JSON. */
22
+ export interface CookbookAppData {
23
+ cookbook: Cookbook;
24
+ /** Only the recipes and models the cookbook uses. */
25
+ catalog: CookbookCatalog;
26
+ /** The validator's warnings. */
27
+ warnings: string[];
28
+ }
29
+ export interface LoadCookbookAppOptions {
30
+ /** Project root; default `process.cwd()`. */
31
+ root?: string;
32
+ /** Cookbook file relative to `root`; default {@link COOKBOOK_FILE}. */
33
+ file?: string;
34
+ /**
35
+ * Packages whose `manifest.json` to read. Default: every dependency of the
36
+ * project's `package.json` that exports `./manifest.json`.
37
+ */
38
+ packages?: readonly string[];
39
+ /**
40
+ * The validator. Default: `validateCookbook` from
41
+ * `@happyvertical/smrt-core/cookbook`, which the app depends on.
42
+ */
43
+ validate?: CookbookValidator;
44
+ }
45
+ /**
46
+ * Read a package's `manifest.json` through its `./manifest.json` (or
47
+ * `./manifest`) export; `null` when the package has none or is not installed.
48
+ */
49
+ export declare function readInstalledManifest(root: string, packageName: string): Promise<CookbookManifest | null>;
50
+ /** Read the manifests of `packages`, skipping those that have none. */
51
+ export declare function readInstalledManifests(root: string, packages?: readonly string[]): Promise<CookbookManifest[]>;
52
+ /**
53
+ * Load and validate the project's cookbook and resolve it against the
54
+ * installed packages' recipes. Throws when the cookbook is missing or invalid
55
+ * (the message lists every problem), including a recipe no installed package
56
+ * declares.
57
+ */
58
+ export declare function loadCookbookApp(options?: LoadCookbookAppOptions): Promise<CookbookAppData>;
59
+ /** Whether `root` has a cookbook file (for apps that fall back to a plain shell). */
60
+ export declare function hasCookbook(root?: string, file?: string): Promise<boolean>;
61
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EACL,KAAK,gBAAgB,EACrB,oBAAoB,EACpB,aAAa,EACb,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAClD,mBAAmB,YAAY,CAAC;AAEhC,eAAO,MAAM,aAAa,uBAAuB,CAAC;AAElD,sFAAsF;AACtF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACrD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEpC,MAAM,MAAM,iBAAiB,GAAG,CAC9B,QAAQ,EAAE,OAAO,EACjB,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CAAE,KACrC,kBAAkB,CAAC;AAExB,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,qDAAqD;IACrD,OAAO,EAAE,eAAe,CAAC;IACzB,gCAAgC;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AA8BD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAclC;AAED,uEAAuE;AACvE,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAC3B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAM7B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,CAAC,CAgC1B;AAED,qFAAqF;AACrF,wBAAsB,WAAW,CAC/B,IAAI,GAAE,MAAsB,EAC5B,IAAI,GAAE,MAAsB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAOlB"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @happyvertical/smrt-svelte/cookbook/server
3
+ *
4
+ * Node-only loading for a cookbook app's `+layout.server.ts` (#3749): read and
5
+ * validate `smrt.cookbook.json`, read the installed packages' manifests and
6
+ * return the plain-JSON data `CookbookApp` takes. Also re-exports the pure,
7
+ * Svelte-free builder so a server can derive the shell without a DOM.
8
+ */
9
+ import { access, readFile } from 'node:fs/promises';
10
+ import { createRequire } from 'node:module';
11
+ import { join, resolve } from 'node:path';
12
+ import { catalogFromManifests, narrowCatalog, } from './catalog.js';
13
+ export { buildCookbookShell } from './build.js';
14
+ export { catalogFromManifests, narrowCatalog, packageIdOf, } from './catalog.js';
15
+ export { resolveCookbookTheme } from './theme.js';
16
+ export const COOKBOOK_FILE = 'smrt.cookbook.json';
17
+ async function readJson(path) {
18
+ return JSON.parse(await readFile(path, 'utf8'));
19
+ }
20
+ async function defaultValidator() {
21
+ try {
22
+ const mod = (await import(
23
+ /* @vite-ignore */ '@happyvertical/smrt-core/cookbook'));
24
+ return mod.validateCookbook;
25
+ }
26
+ catch (error) {
27
+ throw new Error('loadCookbookApp needs @happyvertical/smrt-core (it validates the ' +
28
+ 'cookbook); install it or pass `validate`. ' +
29
+ (error instanceof Error ? error.message : String(error)));
30
+ }
31
+ }
32
+ /** Dependencies of the project that publish a manifest. */
33
+ async function manifestPackages(root) {
34
+ const pkg = (await readJson(join(root, 'package.json')));
35
+ return Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }).sort();
36
+ }
37
+ /**
38
+ * Read a package's `manifest.json` through its `./manifest.json` (or
39
+ * `./manifest`) export; `null` when the package has none or is not installed.
40
+ */
41
+ export async function readInstalledManifest(root, packageName) {
42
+ const require = createRequire(join(resolve(root), 'package.json'));
43
+ for (const subpath of ['manifest.json', 'manifest']) {
44
+ try {
45
+ const path = require.resolve(`${packageName}/${subpath}`);
46
+ const manifest = (await readJson(path));
47
+ if (manifest && typeof manifest === 'object' && manifest.packageName) {
48
+ return manifest;
49
+ }
50
+ }
51
+ catch {
52
+ // Not exported or not installed: try the next subpath.
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ /** Read the manifests of `packages`, skipping those that have none. */
58
+ export async function readInstalledManifests(root, packages) {
59
+ const names = packages ?? (await manifestPackages(root));
60
+ const found = await Promise.all(names.map((name) => readInstalledManifest(root, name)));
61
+ return found.filter((m) => m !== null);
62
+ }
63
+ /**
64
+ * Load and validate the project's cookbook and resolve it against the
65
+ * installed packages' recipes. Throws when the cookbook is missing or invalid
66
+ * (the message lists every problem), including a recipe no installed package
67
+ * declares.
68
+ */
69
+ export async function loadCookbookApp(options = {}) {
70
+ const root = resolve(options.root ?? process.cwd());
71
+ const file = options.file ?? COOKBOOK_FILE;
72
+ const validate = options.validate ?? (await defaultValidator());
73
+ let document;
74
+ try {
75
+ document = await readJson(join(root, file));
76
+ }
77
+ catch (error) {
78
+ throw new Error(`Cannot read ${file}: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ const manifests = await readInstalledManifests(root, options.packages);
81
+ const full = catalogFromManifests(manifests);
82
+ const result = validate(document, {
83
+ recipes: full.recipes.map((r) => r.id),
84
+ });
85
+ if (!result.ok) {
86
+ throw new Error(`${file} is not a valid cookbook:\n- ${result.errors.join('\n- ')}`);
87
+ }
88
+ const { cookbook } = result;
89
+ const warnings = [...(result.warnings ?? [])];
90
+ return {
91
+ cookbook,
92
+ catalog: narrowCatalog(full, {
93
+ recipes: cookbook.recipes,
94
+ features: cookbook.features ?? [],
95
+ }),
96
+ warnings,
97
+ };
98
+ }
99
+ /** Whether `root` has a cookbook file (for apps that fall back to a plain shell). */
100
+ export async function hasCookbook(root = process.cwd(), file = COOKBOOK_FILE) {
101
+ try {
102
+ await access(join(resolve(root), file));
103
+ return true;
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The cookbook's `theme` as the shell applies it (#3749). Pure: registering a
3
+ * brand colour with smrt-ui is the component's job (`brand-theme.ts`).
4
+ */
5
+ import type { CookbookTheme } from '@happyvertical/smrt-types';
6
+ import type { ResolvedCookbookTheme } from './types.js';
7
+ export declare const DEFAULT_THEME_PRESET = "smrt";
8
+ export declare const DEFAULT_COLOR_SCHEME: "system";
9
+ /** The fonts a brand theme may name, each with its CSS stack. A fixed list, never free text. */
10
+ export declare const THEME_FONTS: Readonly<Record<string, string>>;
11
+ /** `#rgb` or `#rrggbb` (with or without `#`) as lowercase `#rrggbb`; else null. */
12
+ export declare function normalizeHex(value: unknown): string | null;
13
+ /** The brand theme's id: unique per colour and font, since a registered id never changes. */
14
+ export declare function brandThemeId(primary: string, fontFamily?: string): string;
15
+ /**
16
+ * The preset and scheme a theme means. Lenient: a bad colour or font is
17
+ * ignored rather than throwing, because a hand-edited file must still render
18
+ * (`validateCookbook` is where it is rejected).
19
+ */
20
+ export declare function resolveCookbookTheme(theme: CookbookTheme | undefined): ResolvedCookbookTheme;
21
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/components/cookbook/theme.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,eAAO,MAAM,oBAAoB,SAAS,CAAC;AAC3C,eAAO,MAAM,oBAAoB,EAAG,QAAiB,CAAC;AAEtD,gGAAgG;AAChG,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxD,CAAC;AAIF,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAO1D;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,qBAAqB,CAmBvB"}
@@ -0,0 +1,53 @@
1
+ export const DEFAULT_THEME_PRESET = 'smrt';
2
+ export const DEFAULT_COLOR_SCHEME = 'system';
3
+ /** The fonts a brand theme may name, each with its CSS stack. A fixed list, never free text. */
4
+ export const THEME_FONTS = {
5
+ 'system-ui': 'system-ui, -apple-system, "Segoe UI", sans-serif',
6
+ Inter: 'Inter, system-ui, sans-serif',
7
+ Georgia: 'Georgia, "Times New Roman", serif',
8
+ 'ui-rounded': 'ui-rounded, "SF Pro Rounded", system-ui, sans-serif',
9
+ 'ui-monospace': 'ui-monospace, "SF Mono", Menlo, monospace',
10
+ };
11
+ const HEX = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
12
+ /** `#rgb` or `#rrggbb` (with or without `#`) as lowercase `#rrggbb`; else null. */
13
+ export function normalizeHex(value) {
14
+ if (typeof value !== 'string')
15
+ return null;
16
+ const match = HEX.exec(value.trim());
17
+ if (!match)
18
+ return null;
19
+ let digits = match[1].toLowerCase();
20
+ if (digits.length === 3)
21
+ digits = [...digits].map((d) => d + d).join('');
22
+ return `#${digits}`;
23
+ }
24
+ /** The brand theme's id: unique per colour and font, since a registered id never changes. */
25
+ export function brandThemeId(primary, fontFamily) {
26
+ const font = fontFamily
27
+ ? `-${fontFamily.toLowerCase().replace(/[^a-z0-9]/g, '')}`
28
+ : '';
29
+ return `cookbook-brand-${primary.replace('#', '')}${font}`;
30
+ }
31
+ /**
32
+ * The preset and scheme a theme means. Lenient: a bad colour or font is
33
+ * ignored rather than throwing, because a hand-edited file must still render
34
+ * (`validateCookbook` is where it is rejected).
35
+ */
36
+ export function resolveCookbookTheme(theme) {
37
+ const colorScheme = theme?.colorScheme === 'light' || theme?.colorScheme === 'dark'
38
+ ? theme.colorScheme
39
+ : DEFAULT_COLOR_SCHEME;
40
+ const primary = theme?.custom ? normalizeHex(theme.custom.primary) : null;
41
+ if (theme?.custom && primary) {
42
+ const font = theme.custom.fontFamily;
43
+ const fontFamily = typeof font === 'string' && Object.hasOwn(THEME_FONTS, font)
44
+ ? font
45
+ : undefined;
46
+ return {
47
+ preset: brandThemeId(primary, fontFamily),
48
+ colorScheme,
49
+ brand: { primary, ...(fontFamily ? { fontFamily } : {}) },
50
+ };
51
+ }
52
+ return { preset: theme?.preset || DEFAULT_THEME_PRESET, colorScheme };
53
+ }