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
@@ -1,5 +1,5 @@
1
1
  import type { PageRecord } from "../store.ts";
2
- import type { PageTree } from "../render.tsx";
2
+ import type { PageTree } from "../page-context.ts";
3
3
  import type { EntryRecord, EntryUsage } from "../entry-store.ts";
4
4
  import type { AssetRecord, AssetMeta } from "../media.ts";
5
5
  import type { AssetPage, AssetQuery, FinishInput, UploadTicket } from "../asset-store.ts";
@@ -289,7 +289,8 @@ export type AdminOps = {
289
289
  };
290
290
  };
291
291
  /** `token`: the editor's Supabase Auth session token, attached by the
292
- * client facade and verified by the server gate (admin/auth.ts). */
292
+ * client facade, sent as the Authorization header and verified by the
293
+ * server gate (admin/auth.ts). */
293
294
  export type AdminOpCall = {
294
295
  path: string;
295
296
  args: unknown[];
@@ -0,0 +1,21 @@
1
+ import type { PresenceConfig } from "../env.ts";
2
+ import type { AdminRegistry } from "./serialize.ts";
3
+ import type { AuthConfig } from "./auth-config.ts";
4
+ export type AdminPageData = {
5
+ registry: AdminRegistry;
6
+ /** What the browser needs to sign in: public values only. */
7
+ auth: AuthConfig;
8
+ /** What the browser needs to join the editing-presence channel; null = presence is off. */
9
+ presence: PresenceConfig | null;
10
+ /** Where operations are posted. */
11
+ opUrl: string;
12
+ /** The folder the package's files are served from, without a trailing slash. */
13
+ filesBase: string;
14
+ /** What the editor puts before a page's address to load its draft: "" where the framework serves drafts
15
+ * at the page's own address (Next), the preview address's base where it does not (Astro). */
16
+ previewBase: string;
17
+ };
18
+ export declare const ADMIN_DATA_ID = "smoodly-admin-data";
19
+ export declare const ADMIN_ROOT_ID = "smoodly-admin-root";
20
+ /** The reserved segment under the admin path: the endpoint and the files live here. */
21
+ export declare const ADMIN_INTERNAL = "_smoodly";
@@ -0,0 +1,4 @@
1
+ export const ADMIN_DATA_ID = "smoodly-admin-data";
2
+ export const ADMIN_ROOT_ID = "smoodly-admin-root";
3
+ /** The reserved segment under the admin path: the endpoint and the files live here. */
4
+ export const ADMIN_INTERNAL = "_smoodly";
@@ -0,0 +1,6 @@
1
+ import { type AdminPageData } from "./page-data.ts";
2
+ /** The theme boot runs inside the wrapper (it reads `currentScript.parentElement`), before first paint;
3
+ * the script then mounts the shell into the wrapper, replacing the boot and the notice. */
4
+ export declare function adminPageHtml(data: AdminPageData): string;
5
+ /** An admin that cannot start: the message, on the admin's own background. */
6
+ export declare function adminErrorHtml(message: string): string;
@@ -0,0 +1,35 @@
1
+ // packages/smoodly/src/admin/page.ts
2
+ // The admin's one HTML page (spec 2026-10-02 astro cloud fit §4): a mount point, the page's data as JSON
3
+ // and the script. The same document for every address under the admin path; the shell reads its route
4
+ // from the URL (shell/router.ts). Framework-free: a string.
5
+ import { ADMIN_DATA_ID, ADMIN_ROOT_ID } from "./page-data.js";
6
+ import { BOOT_CSS, THEME_BOOT } from "./ui/theme.js";
7
+ const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8
+ /** JSON for a <script> element: every `<` escaped, so no value can close the element or open a comment. */
9
+ const scriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c");
10
+ const notice = (text) => `<div style="height:100vh;display:grid;place-items:center;padding:0 24px;text-align:center;font:14px/1.5 system-ui,sans-serif;color:#8A8FA3">${text}</div>`;
11
+ const documentOf = (body) => `<!doctype html>
12
+ <html lang="en">
13
+ <head>
14
+ <meta charset="utf-8">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1">
16
+ <meta name="robots" content="noindex">
17
+ <title>Smoodly</title>
18
+ <style data-smoodly-boot>${BOOT_CSS}</style>
19
+ </head>
20
+ <body>
21
+ ${body}
22
+ </body>
23
+ </html>
24
+ `;
25
+ /** The theme boot runs inside the wrapper (it reads `currentScript.parentElement`), before first paint;
26
+ * the script then mounts the shell into the wrapper, replacing the boot and the notice. */
27
+ export function adminPageHtml(data) {
28
+ return documentOf(`<div class="sm-admin" id="${ADMIN_ROOT_ID}"><script>${THEME_BOOT}</script>${notice("Loading…")}</div>\n` +
29
+ `<script type="application/json" id="${ADMIN_DATA_ID}">${scriptJson(data)}</script>\n` +
30
+ `<script type="module" src="${escapeHtml(data.filesBase)}/admin.js"></script>`);
31
+ }
32
+ /** An admin that cannot start: the message, on the admin's own background. */
33
+ export function adminErrorHtml(message) {
34
+ return documentOf(`<div class="sm-admin">${notice(escapeHtml(message))}</div>`);
35
+ }
@@ -28,11 +28,11 @@ function serializeMap(map) {
28
28
  }
29
29
  /** A registered section's settings descriptors — what a visual global's form shows (spec 2026-09-14 §3). */
30
30
  export function sectionSettings(config, name) {
31
- return (config.registry.sections ?? []).find((s) => s.schema.name === name)?.schema.settings ?? {};
31
+ return config.registry.sections.find((s) => s.name === name)?.settings ?? {};
32
32
  }
33
33
  /** Whether a section gets the custom-CSS box: the project flag, unless the schema opts out (spec 2026-09-14 §4). */
34
34
  function sectionCss(config, name) {
35
- return config.css && (config.registry.sections ?? []).find((s) => s.schema.name === name)?.schema.css !== false;
35
+ return config.css && config.registry.sections.find((s) => s.name === name)?.css !== false;
36
36
  }
37
37
  const lower = (key) => fieldLabel(key).toLowerCase();
38
38
  function seoLabels(c) {
@@ -46,7 +46,7 @@ function seoLabels(c) {
46
46
  export function serializeAdminConfig(config) {
47
47
  const perLocale = (v) => perLocaleSegments(v, config.locales.supported);
48
48
  return {
49
- sections: (config.registry.sections ?? []).map(({ schema }) => {
49
+ sections: config.registry.sections.map((schema) => {
50
50
  const s = schema;
51
51
  return {
52
52
  name: s.name,
@@ -60,7 +60,7 @@ export function serializeAdminConfig(config) {
60
60
  sample: s.sample,
61
61
  };
62
62
  }),
63
- elements: (config.registry.elements ?? []).map(({ schema }) => {
63
+ elements: config.registry.elements.map((schema) => {
64
64
  const e = schema;
65
65
  return {
66
66
  name: e.name,
@@ -84,9 +84,9 @@ export function serializeAdminConfig(config) {
84
84
  ...(c.seo !== undefined ? { seo: seoLabels(c) } : {}),
85
85
  };
86
86
  }),
87
- pages: (config.registry.pages ?? [])
88
- .filter(({ schema }) => schema.kind === "page")
89
- .map(({ schema }) => {
87
+ pages: config.registry.pages
88
+ .filter((schema) => schema.kind === "page")
89
+ .map((schema) => {
90
90
  const p = schema;
91
91
  const slug = fixedSlug(p, config.locales.default);
92
92
  return {
@@ -5,8 +5,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  // list views in the main area, editor/entry views mounted by segment.
6
6
  // Internal routing is the shell's own (shell/router.ts): the route is read
7
7
  // from the URL on mount, then pushState moves between views and the shell
8
- // never reloads (spec 2026-09-08 client routing). The shell mounts from the
9
- // admin LAYOUT (next/create-admin.tsx), never from the catch-all page.
8
+ // never reloads (spec 2026-09-08 client routing). The shell is mounted by
9
+ // the prebuilt entry (browser/main.tsx) into the admin's one HTML page (page.ts).
10
10
  //
11
11
  // Layout is the design system's three columns: the 72px icon rail
12
12
  // (shell/IconRail.tsx), then the main column whose views own their own
@@ -26,7 +26,9 @@ export function ContentHealthPage({ ops, registry, initial = null }) {
26
26
  return _jsx(ErrorPane, { message: error, back: false, onRetry: scan });
27
27
  const sections = report ? healthSections(report, registry) : null;
28
28
  const brokenCount = sections ? sections.broken.length + sections.collections.length : 0;
29
- return (_jsxs(_Fragment, { children: [_jsx(TopBar, { left: _jsx("span", { className: "sm-title", children: "Content health" }), right: _jsxs(_Fragment, { children: [report && _jsx("span", { className: "sm-hint", children: "Checked just now" }), _jsx(Button, { variant: "secondary", onClick: scan, disabled: !report, children: "Rescan" })] }) }), _jsx("div", { className: "sm-listwrap", children: _jsxs("div", { className: "sm-table sm-health", children: [!sections && _jsx("div", { className: "sm-empty", children: "Checking content\u2026" }), sections && brokenCount === 0 && sections.orphan.length === 0 && _jsx("div", { className: "sm-empty", children: "Everything matches the code." }), sections && brokenCount > 0 && (_jsxs("section", { children: [_jsxs("h2", { className: "sm-health__section", children: ["Blocks publishing \u00B7 ", brokenCount] }), sections.collections.map((c) => _jsx("p", { className: "sm-health__headline", children: c.headline }, c.name)), sections.broken.map((g) => _jsx(Group, { group: g }, g.key))] })), sections && sections.orphan.length > 0 && (_jsxs("section", { children: [_jsxs("h2", { className: "sm-health__section", children: ["Left over \u00B7 ", sections.orphan.length] }), sections.orphan.map((g) => _jsx(Group, { group: g }, g.key))] }))] }) })] }));
29
+ return (_jsxs(_Fragment, { children: [_jsx(TopBar, { left: _jsx("span", { className: "sm-title", children: "Content health" }), right: _jsxs(_Fragment, { children: [report && _jsx("span", { className: "sm-hint", children: "Checked just now" }), _jsx(Button, { variant: "secondary", onClick: scan, disabled: !report, children: "Rescan" })] }) }), _jsx("div", { className: "sm-listwrap", children: _jsxs("div", { className: "sm-table sm-health", children: [!sections && _jsx("div", { className: "sm-empty", children: "Checking content\u2026" }), report?.delivery && (_jsx("p", { className: "sm-health__delivery", children: report.delivery.behind === 0
30
+ ? "Every published document is current."
31
+ : _jsxs(_Fragment, { children: [report.delivery.behind, " ", report.delivery.behind === 1 ? "document is" : "documents are", " behind the current stamp. Run ", _jsx("code", { children: "npx smoodly rebuild" }), " in the site's folder."] }) })), sections && brokenCount === 0 && sections.orphan.length === 0 && _jsx("div", { className: "sm-empty", children: "Everything matches the code." }), sections && brokenCount > 0 && (_jsxs("section", { children: [_jsxs("h2", { className: "sm-health__section", children: ["Blocks publishing \u00B7 ", brokenCount] }), sections.collections.map((c) => _jsx("p", { className: "sm-health__headline", children: c.headline }, c.name)), sections.broken.map((g) => _jsx(Group, { group: g }, g.key))] })), sections && sections.orphan.length > 0 && (_jsxs("section", { children: [_jsxs("h2", { className: "sm-health__section", children: ["Left over \u00B7 ", sections.orphan.length] }), sections.orphan.map((g) => _jsx(Group, { group: g }, g.key))] }))] }) })] }));
30
32
  }
31
33
  function Group({ group }) {
32
34
  const adminHref = useAdminHref();
@@ -456,7 +456,7 @@ export function EntryForm({ ops, registry, collection, id }) {
456
456
  return;
457
457
  const r = await ops.entries.delete(collection, id);
458
458
  if (r.ok)
459
- void navigate(adminHref(`/${collection}`), { force: true });
459
+ void navigate(adminHref(`/${collection}`));
460
460
  else
461
461
  fail(r);
462
462
  };
@@ -1,4 +1,3 @@
1
- import { type ConfirmOptions } from "../ui/confirm.ts";
2
1
  export type Route = {
3
2
  pathname: string;
4
3
  segments: string[];
@@ -19,27 +18,15 @@ export declare function isPlainLeftClick(e: {
19
18
  altKey: boolean;
20
19
  defaultPrevented: boolean;
21
20
  }, target?: string | null): boolean;
22
- /** The leave question, asked in the admin's own dialog (ui/confirm.ts). */
23
- export declare const LEAVE_CONFIRM: ConfirmOptions;
24
- /** Leaving is allowed when no guard is dirty, else when the editor
25
- * confirms — asked once, however many guards are dirty. */
26
- export declare function mayLeave(guards: Iterable<() => boolean>, confirm: (options: ConfirmOptions) => boolean | Promise<boolean>): Promise<boolean>;
27
- /** Move to `href` in place; resolves true when the move happened. A push
28
- * asks the leave guard (in the admin's dialog, so the answer is awaited);
29
- * a replace never leaves the view, so it does not ask. `force` skips the
30
- * guard for a view that has just saved or deleted its record — React has
31
- * not flushed its setDirty(false) yet. */
21
+ /** Move to `href` in place; resolves true when the move happened. Every
22
+ * view saves by itself, so nothing asks before leaving. */
32
23
  export declare function navigate(href: string, opts?: {
33
24
  replace?: boolean;
34
- force?: boolean;
35
25
  }): Promise<boolean>;
36
26
  /** The current route under `base`: empty until mount, then the URL,
37
27
  * re-read on popstate and after navigate(). */
38
28
  export declare function useRoute(base: string): Route;
39
- /** Register the calling view as dirty: navigate() asks before leaving,
40
- * and beforeunload covers reload, closing the tab and a real link out. */
41
- export declare function useLeaveGuard(dirty: boolean): void;
42
- /** The tab-close half of useLeaveGuard alone, for a view that saves on
29
+ /** The tab-close guard, for a view that saves on
43
30
  * unmount (the page editor, spec 2026-09-29 pre-install fixes §4): an
44
31
  * in-app navigation loses nothing, so only reload, closing the tab and a
45
32
  * real link out ask, while a change or a save is pending. */
@@ -10,7 +10,6 @@
10
10
  // framework-agnostic.
11
11
  import { useEffect, useState } from "react";
12
12
  import { DEFAULT_ADMIN_PATH } from "../../admin-path.js";
13
- import { confirm } from "../ui/confirm.js";
14
13
  /** The segments under `base`: "/admin/articles/x" → ["articles", "x"];
15
14
  * the mount itself, or a pathname outside it, → []. Segments stay as
16
15
  * written; the shell decodes the ones it uses, as it does for the
@@ -29,24 +28,7 @@ export function isPlainLeftClick(e, target) {
29
28
  return false;
30
29
  return !target || target === "_self";
31
30
  }
32
- /** The leave question, asked in the admin's own dialog (ui/confirm.ts). */
33
- export const LEAVE_CONFIRM = {
34
- title: "Discard unsaved changes?",
35
- body: "Your latest edits have not been saved and will be lost if you leave now.",
36
- confirmLabel: "Discard changes",
37
- cancelLabel: "Stay",
38
- destructive: true,
39
- };
40
- /** Leaving is allowed when no guard is dirty, else when the editor
41
- * confirms — asked once, however many guards are dirty. */
42
- export async function mayLeave(guards, confirm) {
43
- for (const dirty of guards)
44
- if (dirty())
45
- return confirm(LEAVE_CONFIRM);
46
- return true;
47
- }
48
31
  // ── the live router ──────────────────────────────────────────────────
49
- const guards = new Set();
50
32
  const listeners = new Set();
51
33
  function currentRoute(base) {
52
34
  const { pathname, search } = window.location;
@@ -55,14 +37,9 @@ function currentRoute(base) {
55
37
  // The segments derive from the pathname and a base that never changes
56
38
  // under a mounted shell, so two strings decide sameness.
57
39
  const sameRoute = (a, b) => a.pathname === b.pathname && a.search === b.search;
58
- /** Move to `href` in place; resolves true when the move happened. A push
59
- * asks the leave guard (in the admin's dialog, so the answer is awaited);
60
- * a replace never leaves the view, so it does not ask. `force` skips the
61
- * guard for a view that has just saved or deleted its record — React has
62
- * not flushed its setDirty(false) yet. */
40
+ /** Move to `href` in place; resolves true when the move happened. Every
41
+ * view saves by itself, so nothing asks before leaving. */
63
42
  export async function navigate(href, opts = {}) {
64
- if (!opts.replace && !opts.force && !(await mayLeave(guards, confirm)))
65
- return false;
66
43
  window.history[opts.replace ? "replaceState" : "pushState"](null, "", href);
67
44
  for (const listener of listeners)
68
45
  listener();
@@ -81,20 +58,7 @@ export function useRoute(base) {
81
58
  }, [base]);
82
59
  return route;
83
60
  }
84
- /** Register the calling view as dirty: navigate() asks before leaving,
85
- * and beforeunload covers reload, closing the tab and a real link out. */
86
- export function useLeaveGuard(dirty) {
87
- useEffect(() => {
88
- if (!dirty)
89
- return;
90
- const guard = () => true;
91
- guards.add(guard);
92
- const onBeforeUnload = (e) => { e.preventDefault(); e.returnValue = ""; };
93
- window.addEventListener("beforeunload", onBeforeUnload);
94
- return () => { guards.delete(guard); window.removeEventListener("beforeunload", onBeforeUnload); };
95
- }, [dirty]);
96
- }
97
- /** The tab-close half of useLeaveGuard alone, for a view that saves on
61
+ /** The tab-close guard, for a view that saves on
98
62
  * unmount (the page editor, spec 2026-09-29 pre-install fixes §4): an
99
63
  * in-app navigation loses nothing, so only reload, closing the tab and a
100
64
  * real link out ask, while a change or a save is pending. */
@@ -1,9 +1,6 @@
1
1
  import { type Session, type SupabaseClient } from "@supabase/supabase-js";
2
- export type AuthConfig = {
3
- url: string;
4
- key: string;
5
- providers?: ("google" | "github")[];
6
- };
2
+ import type { AuthConfig } from "../auth-config.ts";
3
+ export type { AuthConfig };
7
4
  export type Arrival = "invite" | "recovery";
8
5
  /** What an auth email link says it is: GoTrue's implicit flow puts `type=` in the fragment; a query form is accepted too. */
9
6
  export declare function arrivalOf(hash: string, search: string): Arrival | null;
@@ -11,7 +11,7 @@ export function supabaseAdminAuth(input) {
11
11
  let client = null;
12
12
  const auth = () => {
13
13
  if (!input.authKey) {
14
- throw new Error("smoodly: set SMOODLY_PUBLISHABLE_KEY in .env.local, or in your host's environment variables — the admin signs in with it (Deploying: https://smoodly.io/docs/getting-started/deploying).");
14
+ throw new Error("smoodly: set SMOODLY_PUBLISHABLE_KEY in .env (Astro) or .env.local (Next.js), or in your host's environment variables — the admin signs in with it (Deploying: https://smoodly.io/docs/getting-started/deploying).");
15
15
  }
16
16
  return (client ?? (client = createClient(input.authUrl, input.authKey, { auth: { persistSession: false, autoRefreshToken: false } })));
17
17
  };
@@ -1,4 +1,4 @@
1
- import type { PageTree, TreeNode } from "../render.tsx";
1
+ import type { PageTree, TreeNode } from "../page-context.ts";
2
2
  import type { ZonePolicy } from "../zones.ts";
3
3
  import type { AdminSection, AdminGlobal } from "./serialize.ts";
4
4
  export declare function zoneOf(tree: PageTree, id: string): string | null;
@@ -0,0 +1,12 @@
1
+ export declare const FONT_FILES: readonly [{
2
+ readonly file: "public-sans-latin.woff2";
3
+ readonly source: "public-sans-latin-wght-normal.woff2";
4
+ readonly range: "U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD";
5
+ }, {
6
+ readonly file: "public-sans-latin-ext.woff2";
7
+ readonly source: "public-sans-latin-ext-wght-normal.woff2";
8
+ readonly range: "U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF";
9
+ }];
10
+ /** The @font-face rules with the files' absolute addresses: the same text goes into the admin's own
11
+ * document and into the canvas frame's, where a relative address would resolve against the site page. */
12
+ export declare function fontFaceCss(filesBase: string): string;
@@ -0,0 +1,22 @@
1
+ // packages/smoodly/src/admin/ui/fonts.ts
2
+ // The admin's own font, served by the admin's route from the package's files (plan 2026-10-03 prebuilt
3
+ // admin): no request leaves the site for it. Public Sans, variable weight, two subsets; the browser
4
+ // fetches a subset only when the page shows a character in its range. `source` is the file's name in
5
+ // @fontsource-variable/public-sans, which the build reads (scripts/build-admin.ts).
6
+ export const FONT_FILES = [
7
+ {
8
+ file: "public-sans-latin.woff2",
9
+ source: "public-sans-latin-wght-normal.woff2",
10
+ range: "U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD",
11
+ },
12
+ {
13
+ file: "public-sans-latin-ext.woff2",
14
+ source: "public-sans-latin-ext-wght-normal.woff2",
15
+ range: "U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF",
16
+ },
17
+ ];
18
+ /** The @font-face rules with the files' absolute addresses: the same text goes into the admin's own
19
+ * document and into the canvas frame's, where a relative address would resolve against the site page. */
20
+ export function fontFaceCss(filesBase) {
21
+ return FONT_FILES.map((f) => `@font-face{font-family:'Public Sans';font-style:normal;font-display:swap;font-weight:100 900;src:url(${filesBase}/${f.file}) format('woff2');unicode-range:${f.range}}`).join("\n");
22
+ }
@@ -1,7 +1,16 @@
1
+ /** The sheet colours, named so the page's pre-script background (BOOT_CSS) cannot drift from the tokens. */
2
+ export declare const SHEET_LIGHT = "#FFFFFF";
3
+ export declare const SHEET_DARK = "#1B1E30";
1
4
  export declare const ADMIN_CSS: string;
2
5
  /** Where the theme toggle's choice is remembered, browser-side only. */
3
6
  export declare const THEME_KEY = "smoodly-theme";
4
- /** Which look the developer is comparing (see the warm block): "warm" applies it, anything else is the default. */
5
- export declare const LOOK_KEY = "smoodly-look";
6
- /** One <style> element and the theme boot, rendered once by AdminLayout. */
7
- export declare function AdminStyles(): import("react").JSX.Element;
7
+ /** Applies a remembered choice before first paint, so a dark-mode editor never
8
+ * gets a white flash, and the inspector never jumps to a dragged width. It
9
+ * runs INSIDE the admin wrapper, so `currentScript.parentElement` is that
10
+ * element — no id lookup, and the host document's <html> is left alone. The
11
+ * width test repeats parseStoredWidth (ui/resize.ts) inline: an integer
12
+ * string at or above the minimum, else ignored. */
13
+ export declare const THEME_BOOT: string;
14
+ /** The admin page's background until the script has injected the stylesheet: the sheet colour under the
15
+ * tokens' own light and dark rules, so a dark-mode editor never gets a white flash (admin/page.ts). */
16
+ export declare const BOOT_CSS: string;