smoodly 0.0.45 → 0.0.47

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 (245) hide show
  1. package/README.md +3 -3
  2. package/dist/addresses.d.ts +6 -7
  3. package/dist/addresses.js +6 -4
  4. package/dist/admin/PublicSans-LICENSE.txt +93 -0
  5. package/dist/admin/auth-config.d.ts +6 -0
  6. package/dist/admin/auth-config.js +1 -0
  7. package/dist/admin/browser/fetch-op.d.ts +10 -0
  8. package/dist/admin/browser/fetch-op.js +44 -0
  9. package/dist/admin/browser/main.d.ts +1 -0
  10. package/dist/admin/browser/main.js +21 -0
  11. package/dist/admin/custom-section.d.ts +1 -1
  12. package/dist/admin/drift.d.ts +1 -1
  13. package/dist/admin/editor/EditorView.js +14 -14
  14. package/dist/admin/editor/autosave.js +1 -1
  15. package/dist/admin/editor/canvas-address.d.ts +2 -0
  16. package/dist/admin/editor/canvas-address.js +8 -0
  17. package/dist/admin/editor/overlay-geometry.d.ts +1 -1
  18. package/dist/admin/editor/protocol.d.ts +18 -4
  19. package/dist/admin/editor/protocol.js +15 -1
  20. package/dist/admin/editor/useCanvasBridge.d.ts +1 -1
  21. package/dist/admin/editor/useCanvasBridge.js +5 -6
  22. package/dist/admin/editor/useInlineSession.js +4 -2
  23. package/dist/admin/files.d.ts +11 -0
  24. package/dist/admin/files.generated.js +2 -0
  25. package/dist/admin/files.js +8 -0
  26. package/dist/admin/forms/useRefOptions.d.ts +2 -2
  27. package/dist/admin/forms/useRefOptions.js +2 -2
  28. package/dist/admin/forms/useRichtextEditor.js +4 -4
  29. package/dist/admin/handler.d.ts +40 -0
  30. package/dist/admin/handler.js +151 -0
  31. package/dist/admin/health.d.ts +4 -0
  32. package/dist/admin/index.d.ts +1 -1
  33. package/dist/admin/ops-impl.d.ts +7 -1
  34. package/dist/admin/ops-impl.js +112 -35
  35. package/dist/admin/ops.d.ts +3 -2
  36. package/dist/admin/page-data.d.ts +21 -0
  37. package/dist/admin/page-data.js +4 -0
  38. package/dist/admin/page.d.ts +6 -0
  39. package/dist/admin/page.js +35 -0
  40. package/dist/admin/serialize.js +7 -7
  41. package/dist/admin/shell/AdminApp.js +2 -2
  42. package/dist/admin/shell/ContentHealthPage.js +3 -1
  43. package/dist/admin/shell/EntryForm.js +1 -1
  44. package/dist/admin/shell/router.d.ts +3 -16
  45. package/dist/admin/shell/router.js +3 -39
  46. package/dist/admin/shell/session.d.ts +2 -5
  47. package/dist/admin/supabase-auth.js +1 -1
  48. package/dist/admin/tree-ops.d.ts +1 -1
  49. package/dist/admin/ui/fonts.d.ts +12 -0
  50. package/dist/admin/ui/fonts.js +22 -0
  51. package/dist/admin/ui/theme.d.ts +13 -4
  52. package/dist/admin/ui/theme.js +28 -157
  53. package/dist/assemble.d.ts +60 -0
  54. package/dist/assemble.js +359 -0
  55. package/dist/astro/cache.d.ts +34 -0
  56. package/dist/astro/cache.js +69 -0
  57. package/dist/astro/components/Custom.astro +21 -0
  58. package/dist/astro/components/Guarded.astro +24 -0
  59. package/dist/astro/components/Head.astro +26 -0
  60. package/dist/astro/components/Image.astro +20 -0
  61. package/dist/astro/components/RichText.astro +23 -0
  62. package/dist/astro/components/Section.astro +20 -0
  63. package/dist/astro/components/Video.astro +5 -0
  64. package/dist/astro/components/Zone.astro +19 -0
  65. package/dist/astro/components.d.ts +6 -0
  66. package/dist/astro/components.js +7 -0
  67. package/dist/astro/content-loaders.d.ts +40 -0
  68. package/dist/astro/content-loaders.js +100 -0
  69. package/dist/astro/content.d.ts +9 -0
  70. package/dist/astro/content.js +17 -0
  71. package/dist/astro/custom.d.ts +1 -0
  72. package/dist/astro/custom.js +3 -0
  73. package/dist/astro/defaults.d.ts +20 -0
  74. package/dist/astro/defaults.js +21 -0
  75. package/dist/astro/env-files.d.ts +1 -0
  76. package/dist/astro/env-files.js +21 -0
  77. package/dist/astro/head.d.ts +13 -0
  78. package/dist/astro/head.js +19 -0
  79. package/dist/astro/image.d.ts +27 -0
  80. package/dist/astro/image.js +29 -0
  81. package/dist/astro/index.d.ts +43 -0
  82. package/dist/astro/index.js +111 -0
  83. package/dist/astro/locale-paths.d.ts +30 -0
  84. package/dist/astro/locale-paths.js +38 -0
  85. package/dist/astro/middleware.d.ts +1 -0
  86. package/dist/astro/middleware.js +83 -0
  87. package/dist/astro/preview-document.d.ts +1 -0
  88. package/dist/astro/preview-document.js +18 -0
  89. package/dist/astro/preview.d.ts +10 -0
  90. package/dist/astro/preview.js +72 -0
  91. package/dist/astro/richtext.d.ts +15 -0
  92. package/dist/astro/richtext.js +78 -0
  93. package/dist/astro/routes/admin.d.ts +3 -0
  94. package/dist/astro/routes/admin.js +41 -0
  95. package/dist/astro/routes/page.astro +17 -0
  96. package/dist/astro/routes/preview.astro +11 -0
  97. package/dist/astro/runtime.d.ts +32 -0
  98. package/dist/astro/runtime.js +46 -0
  99. package/dist/astro/scope.d.ts +45 -0
  100. package/dist/astro/scope.js +103 -0
  101. package/dist/astro/server.d.ts +14 -0
  102. package/dist/astro/server.js +45 -0
  103. package/dist/astro/shell.d.ts +67 -0
  104. package/dist/astro/shell.js +63 -0
  105. package/dist/astro/style.d.ts +1 -0
  106. package/dist/astro/style.js +11 -0
  107. package/dist/astro/views-check.d.ts +3 -0
  108. package/dist/astro/views-check.js +19 -0
  109. package/dist/astro/zone.d.ts +39 -0
  110. package/dist/astro/zone.js +33 -0
  111. package/dist/bind.d.ts +19 -0
  112. package/dist/bind.js +34 -0
  113. package/dist/bridge/bridge.d.ts +9 -0
  114. package/dist/bridge/bridge.js +107 -0
  115. package/dist/bridge/entry.d.ts +1 -0
  116. package/dist/bridge/entry.js +5 -0
  117. package/dist/bridge/preview-pill.d.ts +18 -0
  118. package/dist/bridge/preview-pill.js +115 -0
  119. package/dist/cli/import.d.ts +3 -0
  120. package/dist/cli/import.js +10 -1
  121. package/dist/cli/load-collections.js +4 -3
  122. package/dist/cli/load-config.d.ts +4 -0
  123. package/dist/cli/load-config.js +26 -0
  124. package/dist/cli/main.d.ts +5 -1
  125. package/dist/cli/main.js +24 -6
  126. package/dist/cli/migrate.d.ts +13 -0
  127. package/dist/cli/migrate.js +30 -2
  128. package/dist/cli/rebuild.d.ts +23 -0
  129. package/dist/cli/rebuild.js +22 -0
  130. package/dist/cli/space-io.d.ts +15 -1
  131. package/dist/cli/space-io.js +14 -0
  132. package/dist/client.js +5 -0
  133. package/dist/config.d.ts +28 -20
  134. package/dist/config.js +71 -75
  135. package/dist/content/content-api.d.ts +1 -1
  136. package/dist/content/node.d.ts +2 -3
  137. package/dist/content/node.js +2 -1
  138. package/dist/crash-message.d.ts +1 -0
  139. package/dist/crash-message.js +8 -0
  140. package/dist/css.d.ts +4 -0
  141. package/dist/css.js +5 -2
  142. package/dist/custom/CustomSection.d.ts +1 -1
  143. package/dist/custom/CustomSection.js +4 -8
  144. package/dist/custom/css.js +2 -2
  145. package/dist/custom/index.d.ts +6 -10
  146. package/dist/custom/index.js +1 -2
  147. package/dist/custom/layout.d.ts +5 -0
  148. package/dist/custom/layout.js +14 -0
  149. package/dist/document-stamp.d.ts +7 -0
  150. package/dist/document-stamp.js +48 -0
  151. package/dist/entry-page.d.ts +3 -18
  152. package/dist/env.js +1 -1
  153. package/dist/global-id.js +2 -2
  154. package/dist/global.d.ts +5 -2
  155. package/dist/global.js +8 -5
  156. package/dist/image/next-config.d.ts +3 -0
  157. package/dist/image/next-config.js +12 -7
  158. package/dist/image/remote-pattern.d.ts +1 -0
  159. package/dist/image/remote-pattern.js +5 -0
  160. package/dist/index.d.ts +15 -17
  161. package/dist/index.js +10 -19
  162. package/dist/marks.d.ts +7 -0
  163. package/dist/marks.js +7 -0
  164. package/dist/next/admin-route.d.ts +7 -0
  165. package/dist/next/admin-route.js +28 -0
  166. package/dist/next/bridge.d.ts +1 -0
  167. package/dist/next/bridge.js +26 -0
  168. package/dist/next/canvas-boundary.d.ts +1 -1
  169. package/dist/next/canvas-slot.d.ts +1 -0
  170. package/dist/next/canvas-slot.js +4 -3
  171. package/dist/next/index.d.ts +2 -0
  172. package/dist/next/index.js +1 -0
  173. package/dist/next/page-renderer.d.ts +5 -1
  174. package/dist/next/page-renderer.js +19 -33
  175. package/dist/next/preview-pill-define.d.ts +1 -0
  176. package/dist/next/preview-pill-define.js +10 -0
  177. package/dist/next/preview-pill.d.ts +0 -3
  178. package/dist/next/preview-pill.js +7 -50
  179. package/dist/next/queries.d.ts +3 -28
  180. package/dist/next/queries.js +3 -90
  181. package/dist/next/reads.d.ts +2 -0
  182. package/dist/next/reads.js +18 -0
  183. package/dist/page-context.d.ts +89 -0
  184. package/dist/page-context.js +8 -0
  185. package/dist/pairing.d.ts +2 -22
  186. package/dist/project.d.ts +85 -0
  187. package/dist/project.js +228 -0
  188. package/dist/published.d.ts +115 -0
  189. package/dist/published.js +136 -0
  190. package/dist/react.d.ts +61 -0
  191. package/dist/react.js +17 -0
  192. package/dist/read-shapes.d.ts +1 -1
  193. package/dist/reads.d.ts +80 -0
  194. package/dist/reads.js +142 -0
  195. package/dist/refs.d.ts +1 -1
  196. package/dist/registration.d.ts +8 -0
  197. package/dist/registration.js +17 -0
  198. package/dist/render.d.ts +5 -83
  199. package/dist/render.js +5 -10
  200. package/dist/resolve.d.ts +1 -1
  201. package/dist/richtext/index.d.ts +6 -2
  202. package/dist/richtext/index.js +3 -2
  203. package/dist/richtext-href.d.ts +4 -0
  204. package/dist/richtext-href.js +17 -0
  205. package/dist/richtext-render.js +1 -14
  206. package/dist/schema-version.d.ts +1 -1
  207. package/dist/schema-version.js +1 -1
  208. package/dist/section-crash.d.ts +2 -2
  209. package/dist/section-crash.js +2 -7
  210. package/dist/section-wrapper.d.ts +3 -10
  211. package/dist/section-wrapper.js +3 -2
  212. package/dist/site.d.ts +40 -14
  213. package/dist/site.js +199 -284
  214. package/dist/smd-key.d.ts +9 -0
  215. package/dist/smd-key.js +1 -0
  216. package/dist/sql-space.d.ts +5 -0
  217. package/dist/sql-space.js +11 -5
  218. package/dist/sql.d.ts +2 -0
  219. package/dist/sql.js +127 -1
  220. package/dist/store.d.ts +1 -1
  221. package/dist/supabase-published-store.d.ts +45 -0
  222. package/dist/supabase-published-store.js +247 -0
  223. package/dist/supabase-store.d.ts +1 -1
  224. package/dist/toolset.d.ts +2 -4
  225. package/dist/toolset.js +6 -4
  226. package/dist/version.d.ts +1 -1
  227. package/dist/version.js +1 -1
  228. package/dist/view-props.d.ts +40 -0
  229. package/dist/view-props.js +1 -0
  230. package/dist/views.d.ts +36 -0
  231. package/dist/views.js +52 -0
  232. package/dist/zones.d.ts +7 -7
  233. package/dist/zones.js +7 -6
  234. package/migrations/20261007120000_smoodly_v8.sql +154 -0
  235. package/migrations/20261007200000_smoodly_v9.sql +30 -0
  236. package/migrations/20261008120000_smoodly_v10.sql +47 -0
  237. package/package.json +46 -19
  238. package/dist/admin/next/bridge.d.ts +0 -2
  239. package/dist/admin/next/bridge.js +0 -129
  240. package/dist/admin/next/create-admin.d.ts +0 -19
  241. package/dist/admin/next/create-admin.js +0 -44
  242. package/dist/admin/next/index.d.ts +0 -8
  243. package/dist/admin/next/index.js +0 -8
  244. package/dist/admin/next/op-handler.d.ts +0 -18
  245. package/dist/admin/next/op-handler.js +0 -31
package/dist/render.d.ts CHANGED
@@ -1,94 +1,17 @@
1
- import { type ComponentType, type ReactElement, type ReactNode } from "react";
2
- import type { PageAlternate } from "./paths.ts";
1
+ import { type ComponentType, type ReactElement } from "react";
3
2
  import type { PageSchema } from "./page.ts";
4
3
  import type { BuilderMap, Values } from "./schema.ts";
5
4
  import { type StyleValues } from "./styles.ts";
6
- import { type CustomOptions } from "./custom/index.ts";
7
- export type TreeNode = {
8
- id: string;
9
- type: string;
10
- fields: Record<string, unknown>;
11
- /** The developer's knobs (spec 2026-09-14 §3). */
12
- settings?: Record<string, unknown>;
13
- /** Smoodly's styles steps (spec 2026-09-14 §2); absent keys are Default. */
14
- styles?: Record<string, unknown>;
15
- /** The custom-CSS snippet (spec 2026-09-14 §4), scoped by the wrapper. */
16
- css?: string;
17
- };
18
- /** `meta` is the page's own metadata (SEO title, description, og image).
19
- * It rides inside the tree deliberately: trees are already JSONB in
20
- * page_versions, so this needs no DDL change, and page meta becomes
21
- * versioned with content — draft SEO edits stay unpublished, and publish
22
- * moves content and meta together. */
23
- export type PageTree = {
24
- zones: Record<string, TreeNode[]>;
25
- meta?: Record<string, unknown>;
26
- fields?: Record<string, unknown>;
27
- };
28
- /** Where a rendered page sits in the tree — what a view needs for
29
- * breadcrumbs and sub-navigation without a query (spec 2026-09-05 §5).
30
- * `path` is the locale-relative CMS path (what the content API returns);
31
- * `url` is that path through the app's `href` seam — `/fi/yritys` under
32
- * next-intl, `/yritys` without it — so a page shape links the same way
33
- * in both layouts. `fields` is the linked page's own page fields (spec
34
- * 2026-09-10 page fields §3): the published tree's outside draft mode,
35
- * raw (no asset resolution), defaults applied, `{}` for a folder. */
36
- export type PageLink = {
37
- title: string;
38
- path: string;
39
- hasPage: boolean;
40
- fields: Record<string, unknown>;
41
- };
42
- export type PageContextLink = PageLink & {
43
- url: string;
44
- };
45
- /** The page's address in one other locale, `url` through the app's href seam (spec 2026-09-15 §3). */
46
- export type PageContextAlternate = PageAlternate & {
47
- url: string;
48
- };
49
- export type PageContext<Fields = Record<string, unknown>> = {
50
- id: string;
51
- title: string;
52
- path: string;
53
- url: string;
54
- /** The locale the page was rendered in. */
55
- locale: string;
56
- /** The page's own fields, typed from its schema on the view; resolved, defaults applied. */
57
- fields: Fields;
58
- /** Root first, parent last. */
59
- ancestors: ReadonlyArray<PageContextLink>;
60
- children: ReadonlyArray<PageContextLink>;
61
- /** One key per supported locale: the page's `{ slug, path, url }` there, or null where it is
62
- * absent or unpublished (draft mode: absent only). What a language switch links to. */
63
- locales: Readonly<Record<string, PageContextAlternate | null>>;
64
- };
65
- /** The `page` prop of a view rendered outside the site (a test, a preview
66
- * of a shape): a real object, so a view never guards an optional prop, but
67
- * no position in the tree. Frozen: one singleton shared by every such
68
- * render, so a view that pushed onto it would leak into the next. */
69
- export declare const EMPTY_PAGE_CONTEXT: PageContext;
5
+ import { type CustomOptions } from "./custom/CustomSection.tsx";
6
+ import { type EditorWrap, type PageContext, type PageTree, type ZoneContent } from "./page-context.ts";
7
+ export type { TreeNode, PageTree, PageLink, PageContextLink, PageContextAlternate, PageContext, ZoneContent, EditorWrap } from "./page-context.ts";
8
+ export { EMPTY_PAGE_CONTEXT } from "./page-context.ts";
70
9
  type AnyPaired = ComponentType<any> & {
71
10
  schema: {
72
11
  name: string;
73
12
  title?: string;
74
13
  };
75
14
  };
76
- /** The adapter's canvas net (spec 2026-09-27 section guard §5): called per section, in editor mode only. */
77
- export type EditorWrap = (child: ReactNode, section: {
78
- id: string;
79
- title: string;
80
- }) => ReactNode;
81
- export type ZoneContent = {
82
- readonly name: string;
83
- readonly nodes: TreeNode[];
84
- readonly components: ReadonlyMap<string, AnyPaired>;
85
- /** The config's CSS values behind the styles set (spec 2026-09-14 §2). */
86
- readonly styleValues: StyleValues;
87
- readonly editor: boolean;
88
- /** Control flow the guard must let through — the adapter's (Next: unstable_rethrow; spec 2026-09-27 section guard §2). */
89
- readonly rethrow?: (error: unknown) => void;
90
- readonly editorWrap?: EditorWrap;
91
- };
92
15
  export declare function Zone({ of: content }: {
93
16
  of: ZoneContent;
94
17
  }): import("react").JSX.Element | null;
@@ -114,4 +37,3 @@ export declare function renderPage<Zones extends Record<string, unknown>>(Page:
114
37
  rethrow?: (error: unknown) => void;
115
38
  editorWrap?: EditorWrap;
116
39
  }): ReactElement;
117
- export {};
package/dist/render.js CHANGED
@@ -1,19 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Fragment } from "react";
3
3
  import { EMPTY_ATTR, NODE_ATTR, ZONE_ATTR } from "./marks.js";
4
- import { SMD_KEY } from "./section-wrapper.js";
4
+ import { SMD_KEY } from "./smd-key.js";
5
5
  import { DEFAULT_STYLE_VALUES } from "./styles.js";
6
- import { CUSTOM_NODE_TYPE, createCustomSection } from "./custom/index.js";
6
+ import { CUSTOM_NODE_TYPE } from "./custom/index.js";
7
+ import { createCustomSection } from "./custom/CustomSection.js";
8
+ import { EMPTY_PAGE_CONTEXT } from "./page-context.js";
7
9
  import { renderGuarded } from "./section-guard.js";
8
10
  import { PLACEHOLDER_STYLE } from "./section-crash.js";
9
- /** The `page` prop of a view rendered outside the site (a test, a preview
10
- * of a shape): a real object, so a view never guards an optional prop, but
11
- * no position in the tree. Frozen: one singleton shared by every such
12
- * render, so a view that pushed onto it would leak into the next. */
13
- export const EMPTY_PAGE_CONTEXT = Object.freeze({
14
- id: "", title: "", path: "/", url: "/", locale: "", fields: Object.freeze({}), ancestors: Object.freeze([]), children: Object.freeze([]),
15
- locales: Object.freeze({}),
16
- });
11
+ export { EMPTY_PAGE_CONTEXT } from "./page-context.js";
17
12
  export function Zone({ of: content }) {
18
13
  // A malformed tree (bad data, a client that skipped isPageTree) must not
19
14
  // crash the render — treat a non-array nodes as empty rather than throw.
package/dist/resolve.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Descriptor } from "./fields.ts";
2
2
  import type { CollectionSchema } from "./collections.ts";
3
- import type { PageTree } from "./render.tsx";
3
+ import type { PageTree } from "./page-context.ts";
4
4
  import { type GlobalSchema } from "./global.ts";
5
5
  import { type AssetRecord } from "./media.ts";
6
6
  import { type ElementSchemas } from "./richtext-walk.ts";
@@ -6,7 +6,11 @@ export type CreateRichTextOptions = {
6
6
  /** next/image `sizes` for body images; the site's text column width. */
7
7
  sizes?: string;
8
8
  };
9
- export declare function createRichText({ elements, sizes }?: CreateRichTextOptions): (props: {
9
+ /** The bound renderer; `elements` is the list it renders, which defineViews checks against the config's richtext elements. */
10
+ export type BoundRichText = ((props: {
10
11
  doc: unknown;
11
- }) => ReactElement;
12
+ }) => ReactElement) & {
13
+ elements: RichTextElement[];
14
+ };
15
+ export declare function createRichText({ elements, sizes }?: CreateRichTextOptions): BoundRichText;
12
16
  export type { RichTextElement, RichTextImageProps, RichTextProps } from "../richtext-render.tsx";
@@ -3,7 +3,8 @@ import { RichText } from "../richtext-render.js";
3
3
  import { SmoodlyImage } from "../image/SmoodlyImage.js";
4
4
  export function createRichText({ elements = [], sizes } = {}) {
5
5
  const image = ({ image, caption }) => (_jsxs("figure", { children: [_jsx(SmoodlyImage, { image: image, sizes: sizes }), caption !== "" && _jsx("figcaption", { children: caption })] }));
6
- return function BoundRichText({ doc }) {
6
+ function Bound({ doc }) {
7
7
  return _jsx(RichText, { doc: doc, elements: elements, image: image });
8
- };
8
+ }
9
+ return Object.assign(Bound, { elements });
9
10
  }
@@ -0,0 +1,4 @@
1
+ import type { ResolvedImage } from "./media.ts";
2
+ export declare function safeHref(raw: unknown): string | null;
3
+ export declare function escapeHtml(text: string): string;
4
+ export declare const isResolvedImage: (v: unknown) => v is ResolvedImage;
@@ -0,0 +1,17 @@
1
+ /** Only schemes an editor's link may carry reach the page: http(s), mailto, tel, and site-relative targets.
2
+ * Anything else (javascript:, data:, an empty href, a protocol-relative `//host` or a backslash form
3
+ * `/\host` that a browser treats the same way) renders the text without an anchor. */
4
+ const SAFE_HREF = /^(https?:|mailto:|tel:|\/(?![/\\])|#|\?|\.)/i;
5
+ export function safeHref(raw) {
6
+ if (typeof raw !== "string")
7
+ return null;
8
+ // URL parsers drop tab, LF and CR, so normalise before testing to see what the parser sees.
9
+ const href = raw.replace(/[\t\n\r]/g, "").trim();
10
+ return href !== "" && SAFE_HREF.test(href) ? href : null;
11
+ }
12
+ const ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#x27;" };
13
+ export function escapeHtml(text) {
14
+ return text.replace(/[&<>"']/g, (c) => ESCAPES[c]);
15
+ }
16
+ export const isResolvedImage = (v) => typeof v === "object" && v !== null && typeof v.url === "string"
17
+ && typeof v.width === "number" && typeof v.height === "number";
@@ -9,26 +9,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
9
9
  // (default: a figure over a plain <img>); an `element` node renders the
10
10
  // paired component named in `elements`, nothing when unknown.
11
11
  import { Fragment, createElement } from "react";
12
+ import { isResolvedImage, safeHref } from "./richtext-href.js";
12
13
  import { ELEMENT_NODE, IMAGE_NODE } from "./richtext-walk.js";
13
- /** Only schemes an editor's link may carry reach the page: http(s), mailto, tel,
14
- * and site-relative targets. Anything else (javascript:, data:, an empty href,
15
- * a protocol-relative `//host` or a backslash form `/\host` that a browser
16
- * treats the same way) renders the text without an anchor. */
17
- const SAFE_HREF = /^(https?:|mailto:|tel:|\/(?![/\\])|#|\?|\.)/i;
18
- function safeHref(raw) {
19
- if (typeof raw !== "string")
20
- return null;
21
- // URL parsers drop tab, LF and CR, so normalise before testing to see what the parser sees.
22
- const href = raw.replace(/[\t\n\r]/g, "").trim();
23
- return href !== "" && SAFE_HREF.test(href) ? href : null;
24
- }
25
14
  /** Lazy by default: body images sit below the fold, and a lazy <img> is
26
15
  * the one React does not hoist a preload hint for on the server. */
27
16
  export function defaultRichTextImage({ image, caption }) {
28
17
  return (_jsxs("figure", { children: [_jsx("img", { src: image.url, width: image.width, height: image.height, alt: image.alt, loading: "lazy" }), caption !== "" && _jsx("figcaption", { children: caption })] }));
29
18
  }
30
- const isResolvedImage = (v) => typeof v === "object" && v !== null && typeof v.url === "string"
31
- && typeof v.width === "number" && typeof v.height === "number";
32
19
  export function RichText({ doc, elements = [], image = defaultRichTextImage }) {
33
20
  const d = doc;
34
21
  if (!d || typeof d !== "object" || d.type !== "doc" || !Array.isArray(d.content))
@@ -1,7 +1,7 @@
1
1
  /** The minimum schema version the package needs. Moves ONLY in a release
2
2
  * that adds a table, column, function or policy the package reads; a
3
3
  * unit test holds it equal to the highest file in `migrations/`. */
4
- export declare const SCHEMA_VERSION = 7;
4
+ export declare const SCHEMA_VERSION = 10;
5
5
  export declare const SCHEMA_VERSION_KEY = "schema_version";
6
6
  export declare const CORE_FILE_RE: RegExp;
7
7
  export declare const VIEWS_FILE_RE: RegExp;
@@ -7,7 +7,7 @@
7
7
  /** The minimum schema version the package needs. Moves ONLY in a release
8
8
  * that adds a table, column, function or policy the package reads; a
9
9
  * unit test holds it equal to the highest file in `migrations/`. */
10
- export const SCHEMA_VERSION = 7;
10
+ export const SCHEMA_VERSION = 10;
11
11
  export const SCHEMA_VERSION_KEY = "schema_version";
12
12
  export const CORE_FILE_RE = /^(\d{14})_smoodly_v(\d+)\.sql$/;
13
13
  export const VIEWS_FILE_RE = /^(\d{14})_smoodly_views\.sql$/;
@@ -1,8 +1,8 @@
1
1
  import type { CSSProperties } from "react";
2
+ import { crashMessage } from "./crash-message.ts";
3
+ export { crashMessage };
2
4
  /** The canvas placeholders' look — the unknown section's too (spec 2026-09-27 editor safety §5). */
3
5
  export declare const PLACEHOLDER_STYLE: CSSProperties;
4
- /** Why it crashed, for the placeholder: the message, or the digest when a production build stripped the message. */
5
- export declare function crashMessage(error: unknown): string;
6
6
  export declare function CrashPlaceholder({ id, title, error, live }: {
7
7
  id: string;
8
8
  title: string;
@@ -1,16 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { crashMessage } from "./crash-message.js";
2
3
  import { CRASHED_ATTR, NODE_ATTR } from "./marks.js";
4
+ export { crashMessage };
3
5
  /** The canvas placeholders' look — the unknown section's too (spec 2026-09-27 editor safety §5). */
4
6
  export const PLACEHOLDER_STYLE = {
5
7
  padding: "24px 16px", border: "1px dashed #b54708", color: "#b54708", font: "500 14px/1.4 system-ui, sans-serif", textAlign: "center",
6
8
  };
7
- /** Why it crashed, for the placeholder: the message, or the digest when a production build stripped the message. */
8
- export function crashMessage(error) {
9
- const digest = typeof error === "object" && error !== null ? error.digest : undefined;
10
- if (typeof digest === "string")
11
- return `the server log has the error (digest ${digest})`;
12
- return error instanceof Error ? error.message : String(error);
13
- }
14
9
  export function CrashPlaceholder({ id, title, error, live }) {
15
10
  return (_jsx("div", { [NODE_ATTR]: id, [CRASHED_ATTR]: live, style: PLACEHOLDER_STYLE, children: `Section “${title}” failed to render — ${crashMessage(error)}` }));
16
11
  }
@@ -1,14 +1,7 @@
1
1
  import type { CSSProperties, ReactNode } from "react";
2
- import { type StyleProps, type StyleValues } from "./styles.ts";
3
- /** The reserved key `<Zone>` and `getGlobal` ride on the styles object. */
4
- export declare const SMD_KEY = "__smd";
5
- export type SmdInternal = {
6
- node?: string;
7
- css?: string;
8
- values?: StyleValues;
9
- /** Pre-scoped layout CSS (the custom section, spec 2026-09-14 §6), emitted before the editor's css so the box wins. */
10
- layoutCss?: string;
11
- };
2
+ import { type StyleProps } from "./styles.ts";
3
+ import { SMD_KEY, type SmdInternal } from "./smd-key.ts";
4
+ export { SMD_KEY, type SmdInternal };
12
5
  export declare function Section({ as, styles, className, style, children, ...rest }: {
13
6
  as?: keyof React.JSX.IntrinsicElements;
14
7
  styles: StyleProps & {
@@ -2,6 +2,7 @@ import { createElement } from "react";
2
2
  import { NODE_ATTR } from "./marks.js";
3
3
  import { DEFAULT_STYLE_VALUES, inlineStyles } from "./styles.js";
4
4
  import { safeCss, scopedCss } from "./css.js";
5
+ import { SMD_KEY } from "./smd-key.js";
5
6
  // The root wrapper every section renders through (spec 2026-09-14 §2, §4).
6
7
  // Owns the real root element, no extra div: element choice via `as`,
7
8
  // Smoodly's styles set rendered INLINE (an editor's step beats the
@@ -9,8 +10,8 @@ import { safeCss, scopedCss } from "./css.js";
9
10
  // node id as data-smd-node always (the custom-CSS scope needs it on the
10
11
  // live site), the scoped <style> child, and every other prop spread onto
11
12
  // the root — settings never become attributes; the view writes its own.
12
- /** The reserved key `<Zone>` and `getGlobal` ride on the styles object. */
13
- export const SMD_KEY = "__smd";
13
+ // The reserved key lives in smd-key.ts (pure), re-exported here.
14
+ export { SMD_KEY };
14
15
  export function Section({ as = "section", styles, className, style, children, ...rest }) {
15
16
  const { [SMD_KEY]: smd, ...steps } = (styles ?? {});
16
17
  const inline = inlineStyles(steps, smd?.values ?? DEFAULT_STYLE_VALUES);
package/dist/site.d.ts CHANGED
@@ -4,18 +4,31 @@ import type { Descriptor } from "./fields.ts";
4
4
  import { type EntryRecord, type EntryStore, type Where } from "./entry-store.ts";
5
5
  import { type LocaleAlternate, type LocaleSet, type PageAlternate, type PathTarget } from "./paths.ts";
6
6
  import type { SectionLike } from "./refs.ts";
7
- import type { PageContext, PageLink, PageTree } from "./render.tsx";
8
- import { type GlobalSchema } from "./global.ts";
9
- import { type StyleValues, type StyleValuesInput } from "./styles.ts";
10
- import { type CustomConfigInput, type ResolvedCustom } from "./custom/index.ts";
7
+ import type { PageContext, PageLink, PageTree } from "./page-context.ts";
8
+ import type { GlobalSchema } from "./global.ts";
9
+ import type { StyleValues, StyleValuesInput } from "./styles.ts";
10
+ import type { CustomConfigInput, ResolvedCustom } from "./custom/index.ts";
11
11
  import type { PageRecord, PageStore } from "./store.ts";
12
12
  import type { PathHit } from "./path-index.ts";
13
13
  import { type EntryItem, type PageItem, type Seo } from "./read-shapes.ts";
14
+ import type { SchemaOr } from "./registration.ts";
15
+ /** An address read with its layout's globals: the hit as `lookup` answers it, and each item's value by name. */
16
+ export type AddressWithGlobals = {
17
+ hit: SiteHit | {
18
+ kind: "redirect";
19
+ target: PathTarget;
20
+ } | null;
21
+ globals: Map<string, unknown>;
22
+ };
23
+ import type { PublishedStore } from "./published.ts";
24
+ import { type Projector } from "./project.ts";
14
25
  export type SiteOptions = {
26
+ /** `published`: the delivery documents outside draft mode read (spec 2026-10-07 delivery model §3). */
15
27
  stores: () => {
16
28
  pages: PageStore;
17
29
  entries: EntryStore;
18
30
  assets?: AssetStore;
31
+ published: PublishedStore;
19
32
  };
20
33
  collections: CollectionSchema<any>[];
21
34
  sections: SectionLike[];
@@ -31,18 +44,16 @@ export type SiteOptions = {
31
44
  /** Page shapes (entry pages are ignored): the source of each template's page-field descriptors (spec 2026-09-10 page fields §3)
32
45
  * and of the fixed pages' addresses (spec 2026-09-29 nested paths §3) — `config.registry.pages`. Required: a site that
33
46
  * missed one would take its code-owned record for an orphan and drop its URLs at the first lookup. */
34
- pages: ReadonlyArray<SitePageSchema>;
47
+ pages: ReadonlyArray<SchemaOr<SitePageSchema>>;
35
48
  };
36
- /** A page registration as the site needs it: the template's page fields, and a fixed page's address. */
49
+ /** A page type as the site needs it: its page fields, and a fixed page's address. */
37
50
  export type SitePageSchema = {
38
- schema: {
39
- kind: string;
40
- name: string;
41
- title?: string;
42
- slug?: string | Record<string, string>;
43
- fields?: Record<string, Descriptor>;
44
- seo?: true;
45
- };
51
+ kind: string;
52
+ name: string;
53
+ title?: string;
54
+ slug?: string | Record<string, string>;
55
+ fields?: Record<string, Descriptor>;
56
+ seo?: true;
46
57
  };
47
58
  export type SitePage = {
48
59
  kind: "page";
@@ -111,6 +122,8 @@ export interface SmoodlySite {
111
122
  /** The package version and a hash of the registered model: a part of every cache key, so an upgrade or a
112
123
  * model change never serves a unit cached in the old shape (spec 2026-09-30 tester findings §3). */
113
124
  readonly cacheKey: string;
125
+ /** The document stamp a delivery row carries (DESIGN §3): the format number and the model hash, never the version. */
126
+ readonly stamp: string;
114
127
  /** The resolved styles values the renderer and `getGlobal` hand the wrapper. */
115
128
  readonly styles: StyleValues;
116
129
  /** The custom section's resolved options the renderer hands renderPage. */
@@ -122,6 +135,17 @@ export interface SmoodlySite {
122
135
  /** Everything the renderer needs for one target: null when it is a folder, unpublished (outside draft), or gone. */
123
136
  load(target: PathTarget, locale: string, path: string, options?: LoadOptions): Promise<SiteHit | null>;
124
137
  resolve(locale: string, path: string, options?: LoadOptions): Promise<SiteHit | null>;
138
+ /** The published read of an address (spec 2026-10-07 delivery model §3): one lookup of the stored document; on a miss
139
+ * the code-owned page, then one locate that tells a redirect row from a record to assemble and from a 404. */
140
+ lookup(locale: string, path: string): Promise<SiteHit | {
141
+ kind: "redirect";
142
+ target: PathTarget;
143
+ } | null>;
144
+ /** The same lookup with the layout's global items riding along (schema v9): one store call answers the page and
145
+ * each item's value by name, a row behind the model healed as `getGlobal` heals it. A miss brings no globals. */
146
+ lookupWithGlobals(locale: string, path: string, items: readonly GlobalSchema<any>[]): Promise<AddressWithGlobals>;
147
+ /** Writes delivery rows from the editing tables: Publish calls it, and heal on read goes through it. */
148
+ readonly projector: Projector;
125
149
  /** A fixed page type (or a collection's index page) as code alone describes it, when `path` is its address in
126
150
  * `locale`: an empty tree, the page fields' defaults, the registration's title. The renderer serves it where
127
151
  * the index has no row — a page type added after install, a locale added to the config — until the admin
@@ -152,5 +176,7 @@ export interface SmoodlySite {
152
176
  draft?: boolean;
153
177
  }): Promise<V | null>;
154
178
  }
179
+ /** The `page` prop for a loaded page. `href` is the app's seam from a
180
+ * locale-relative path to a site URL; the default keeps CMS paths as URLs. */
155
181
  export declare function pageContextOf(hit: SitePage, href?: (path: string, locale: string) => string): PageContext;
156
182
  export declare function createSmoodlySite(options: SiteOptions): SmoodlySite;