smoodly 0.0.62 → 0.0.63

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 (61) hide show
  1. package/dist/admin/client-ops.js +1 -1
  2. package/dist/admin/drift.js +3 -1
  3. package/dist/admin/editor/EditorView.js +65 -9
  4. package/dist/admin/editor/InsertPicker.d.ts +8 -27
  5. package/dist/admin/editor/InsertPicker.js +56 -47
  6. package/dist/admin/editor/SamplePreview.d.ts +7 -0
  7. package/dist/admin/editor/SamplePreview.js +86 -0
  8. package/dist/admin/editor/SectionIcon.d.ts +4 -0
  9. package/dist/admin/editor/SectionIcon.js +8 -0
  10. package/dist/admin/editor/StructureView.d.ts +2 -2
  11. package/dist/admin/editor/StructureView.js +2 -1
  12. package/dist/admin/editor/canvas-address.d.ts +2 -0
  13. package/dist/admin/editor/canvas-address.js +3 -1
  14. package/dist/admin/editor/picker-rows.d.ts +33 -0
  15. package/dist/admin/editor/picker-rows.js +38 -0
  16. package/dist/admin/editor/sample-frame.d.ts +36 -0
  17. package/dist/admin/editor/sample-frame.js +58 -0
  18. package/dist/admin/editor/sample-picks.d.ts +34 -0
  19. package/dist/admin/editor/sample-picks.js +49 -0
  20. package/dist/admin/editor/view-memory.d.ts +5 -0
  21. package/dist/admin/editor/view-memory.js +19 -0
  22. package/dist/admin/files.generated.js +1 -1
  23. package/dist/admin/forms/MediaWidget.d.ts +1 -1
  24. package/dist/admin/forms/MediaWidget.js +10 -2
  25. package/dist/admin/ops.d.ts +5 -0
  26. package/dist/admin/shell/GlobalForm.js +1 -1
  27. package/dist/admin/shell/GlobalList.js +1 -1
  28. package/dist/admin/shell/NavBar.js +1 -1
  29. package/dist/admin/shell/PagesList.js +4 -4
  30. package/dist/admin/shell/console-links.js +1 -1
  31. package/dist/admin/shell/health-list.js +1 -1
  32. package/dist/admin/tree-ops.d.ts +11 -4
  33. package/dist/admin/tree-ops.js +28 -17
  34. package/dist/admin/ui/theme.js +32 -13
  35. package/dist/admin/validate.js +3 -2
  36. package/dist/api-client.js +12 -2
  37. package/dist/astro/middleware.js +2 -0
  38. package/dist/astro/shell.d.ts +3 -0
  39. package/dist/astro/shell.js +8 -1
  40. package/dist/canvas-marker.d.ts +5 -0
  41. package/dist/canvas-marker.js +18 -6
  42. package/dist/conditions.js +5 -2
  43. package/dist/media.d.ts +15 -2
  44. package/dist/media.js +68 -10
  45. package/dist/next/page-renderer.js +12 -5
  46. package/dist/resolve.d.ts +6 -1
  47. package/dist/resolve.js +7 -7
  48. package/dist/sample-fill.d.ts +40 -0
  49. package/dist/sample-fill.js +145 -0
  50. package/dist/sample-tree.d.ts +30 -0
  51. package/dist/sample-tree.js +76 -0
  52. package/dist/site.d.ts +13 -0
  53. package/dist/site.js +10 -1
  54. package/dist/version.d.ts +1 -1
  55. package/dist/version.js +1 -1
  56. package/docs/content-modeling/sections.md +19 -3
  57. package/docs/content-modeling/zones.md +2 -2
  58. package/docs/getting-started/working-together.md +1 -1
  59. package/docs/globals/defining.md +1 -1
  60. package/docs/globals/header-and-footer.md +1 -1
  61. package/package.json +1 -1
package/dist/site.d.ts CHANGED
@@ -12,6 +12,7 @@ import type { StyleValues, StyleValuesInput } from "./styles.ts";
12
12
  import type { CustomConfigInput, ResolvedCustom } from "./custom/index.ts";
13
13
  import type { PageRecord, PageStore } from "./store.ts";
14
14
  import type { PathHit } from "./path-index.ts";
15
+ import type { SamplePicks } from "./sample-fill.ts";
15
16
  import type { EntryItem, PageItem, Seo } from "./read-shapes.ts";
16
17
  import type { SchemaOr } from "./registration.ts";
17
18
  import type { ResolvedConfig } from "./config.ts";
@@ -144,6 +145,16 @@ export interface ContentReads {
144
145
  targetPaths(target: PathTarget): Promise<Record<string, string>>;
145
146
  /** Everything the renderer needs for one target: null when it is a folder, unpublished (outside draft), or gone. */
146
147
  load(target: PathTarget, locale: string, path: string, options?: LoadOptions): Promise<SiteHit | null>;
148
+ /** The picker's rows for a zone of a page type, as raw nodes built from the registry's samples (sample-tree.ts):
149
+ * what `loadSamples` is handed. A static: the service holds no samples. */
150
+ sampleTree(template: string, zone: string, picks?: SamplePicks): PageTree;
151
+ /** The collections the zone's offered sections reference (spec 2026-11-14 filled samples): the picks are their
152
+ * first published entries, read before the tree is built. A static. */
153
+ sampleCollections(template: string, zone: string): string[];
154
+ /** The picker's samples (spec 2026-11-14 add section previews): the page's draft with `tree`'s zones resolved in
155
+ * place of the stored ones, images, references and placed globals as a draft has them. Draft only; nothing is
156
+ * stored. A placement names its item alone. Null for an entry, a page that is gone or not in `locale`. */
157
+ loadSamples(target: PathTarget, locale: string, path: string, tree: PageTree): Promise<SiteHit | null>;
147
158
  /** The published read of an address: one locate that tells a record to assemble from a redirect row, and the
148
159
  * code-owned page where the index has no row. */
149
160
  lookup(locale: string, path: string): Promise<SiteHit | {
@@ -220,6 +231,8 @@ export declare function siteStatics(options: Omit<SiteOptions, "stores">): {
220
231
  custom: ResolvedCustom;
221
232
  cacheKey: string;
222
233
  codePage: (locale: string, path: string) => SitePage | null;
234
+ sampleTree: (template: string, zone: string, picks?: SamplePicks) => PageTree;
235
+ sampleCollections: (template: string, zone: string) => string[];
223
236
  };
224
237
  /** What createSmoodlySite takes, read off the config. */
225
238
  export declare function siteOptions(config: ResolvedConfig, stores?: SiteOptions["stores"]): SiteOptions;
package/dist/site.js CHANGED
@@ -2,6 +2,7 @@ import { lastPart } from "./addresses.js";
2
2
  import { withDefaults } from "./field-defaults.js";
3
3
  import { modelKey } from "./model-key.js";
4
4
  import { SMOODLY_VERSION } from "./version.js";
5
+ import { sampleTree as buildSampleTree, zoneSampleCollections } from "./sample-tree.js";
5
6
  import { pageSeo } from "./seo.js";
6
7
  import { pageFieldsOf, siteShape } from "./assemble.js";
7
8
  /** The `page` prop for a loaded page. `href` is the app's seam from a
@@ -60,7 +61,15 @@ export function siteStatics(options) {
60
61
  ...(withSeo ? { seo: pageSeo(undefined, spec.title) } : {}),
61
62
  };
62
63
  }
63
- return { shape, locales, homePageSlug, styles, custom, cacheKey, codePage };
64
+ const sampleSource = {
65
+ sections: options.sections.map((x) => ("schema" in x ? x.schema : x)),
66
+ globals: (options.globals ?? []).map((g) => ({ name: g.name, ...(g.section !== undefined ? { section: g.section } : {}) })),
67
+ };
68
+ // A page type's schema carries its zones (page.ts); the site's own page type names only what the reads use.
69
+ const policyOf = (template, zone) => pageSchemas.find((x) => x.kind === "page" && x.name === template)?.zones?.[zone];
70
+ const sampleTree = (template, zone, picks) => buildSampleTree(sampleSource, policyOf(template, zone), zone, picks);
71
+ const sampleCollections = (template, zone) => zoneSampleCollections(sampleSource, policyOf(template, zone));
72
+ return { shape, locales, homePageSlug, styles, custom, cacheKey, codePage, sampleTree, sampleCollections };
64
73
  }
65
74
  /** What createSmoodlySite takes, read off the config. */
66
75
  export function siteOptions(config, stores) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SMOODLY_VERSION = "0.0.62";
1
+ export declare const SMOODLY_VERSION = "0.0.63";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/write-version.mjs from package.json — do not edit.
2
- export const SMOODLY_VERSION = "0.0.62";
2
+ export const SMOODLY_VERSION = "0.0.63";
@@ -32,7 +32,7 @@ export const heroSchema = smoodly.schema.section({
32
32
  | --- | --- |
33
33
  | `name` | What the content stores for every placed Hero, so keep it stable once pages exist. |
34
34
  | `title` | The label in Add section and in the inspector. |
35
- | `description` | One line under the title in Add section. Optional, like icon. |
35
+ | `description` | A line about the section, kept for later use: the admin does not show it today. Optional, like icon. |
36
36
  | `fields` | What editors fill. Field order is form order: editors see Kicker, Heading, Text, Buttons, Image, top to bottom. |
37
37
  | `sample` | What a new Hero starts with. See Sample content below. |
38
38
 
@@ -195,12 +195,28 @@ A registered section that no zone lists never appears in *Add section*. Lorem Co
195
195
 
196
196
  ## Sample content
197
197
 
198
- `sample` is what a section starts with, so a page never shows an empty one. Smoodly uses it twice:
198
+ `sample` is what a section starts with, so a page never shows an empty one. Smoodly uses it three times:
199
199
 
200
+ - *Add section* shows each section as your site draws it with its sample, so an editor sees what they are about to add.
200
201
  - When an editor picks the section in *Add section*, the new section carries the sample.
201
202
  - When a page opens with an empty locked zone, Smoodly fills the zone from the locked section's sample.
202
203
 
203
- Fields the sample leaves out start from their `.default()`, or empty. A missing image or number stays unset rather than showing a broken picture or a stray zero. Make the sample a complete value in plain copy: it is what editors see first, and what they overwrite.
204
+ What Smoodly shows is what it inserts: the picture and the new section are built by the same rules.
205
+
206
+ The sample always comes first. A field it sets is taken as it is, and nothing is added to it or inside it. A field it leaves out starts from its `.default()`. A field with neither is filled by Smoodly, so a section with a thin sample still looks like itself:
207
+
208
+ | Name | Description |
209
+ | --- | --- |
210
+ | `f.text()` | Required: a short Latin phrase where the key reads as a title (title, heading, label, name, button and the like), else one sentence, cut to .max(n). Optional: empty. |
211
+ | `f.richtext()` | Required: a heading, two paragraphs and a list where the toolset has them, else one paragraph. Optional: empty. |
212
+ | `f.image()` | A placeholder picture, required or optional. It shows in the editor and in previews only, never on the live page; publishing treats it as no image, so a required image still asks for a photo. |
213
+ | `f.ref(), f.refList()` | The collection's first published entries: one for a required f.ref, up to three for an f.refList, fewer if .max(n) is lower. |
214
+ | `f.list(item)` | Required: .min(n) items, or three, never more than .max(n). Each item is filled the same way. |
215
+ | `f.object({ … })` | Required: its fields, filled the same way. |
216
+
217
+ Numbers, dates, links, videos and files are not filled (a number, a video and a file stay unset; a date and a link start empty), a list nothing fills starts as an empty list, and settings are never filled: they keep their defaults. An empty list in a sample (`people: []`) counts as left out. To choose the content yourself, put it in the sample.
218
+
219
+ *Add section* has an *Insert filled* switch under its list. Turned off, a section arrives empty: without the sample and without anything generated. The picture still shows the filled section.
204
220
 
205
221
  The sample may set settings too, under their own keys.
206
222
 
@@ -58,7 +58,7 @@ The hero zone offers a choice of two openers and takes one of them. The body tak
58
58
 
59
59
  ## What editors see
60
60
 
61
- In the canvas, *Add section* appears between the sections of a zone and at its end. It opens a picker with the sections the policy allows, then *Custom section* where `custom: true`, then a *Global* group with the globals whose section is allowed there. A zone capped with `.max(n)` loses its *Add section* once it is full.
61
+ In the canvas, *Add section* appears between the sections of a zone and at its end. It opens a picker with the sections the policy allows, then *Custom section* where `custom: true`, then a *Global* group with the globals whose section is allowed there. A zone capped with `.max(n)` loses its *Add section* once it is full. The picker shows each section as your site draws it, filled where its `sample` is silent; its *Insert filled* switch, when turned off, inserts the section empty instead ([Sample content](../content-modeling/sections.md#sample-content)).
62
62
 
63
63
  A section's toolbar moves it up or down, duplicates it or removes it. In a locked zone the section is tagged *locked* and the toolbar says *fields only*. Smoodly fills the zone from the section's `sample` the first time the page opens.
64
64
 
@@ -66,7 +66,7 @@ An empty zone shows as a box in the canvas so it can take a section. On the site
66
66
 
67
67
  ## Locking a zone to a global
68
68
 
69
- `z.locked()` also takes a global with a section, as `closing` does above. The zone then shows that one global on every page of the type. Editors change it once, under *Sections* or right in any canvas that shows it, and every page follows. The starter's header and footer take a different route: the layout renders them directly, so they need no zone.
69
+ `z.locked()` also takes a global with a section, as `closing` does above. The zone then shows that one global on every page of the type. Editors change it once, under *Globals* or right in any canvas that shows it, and every page follows. The starter's header and footer take a different route: the layout renders them directly, so they need no zone.
70
70
 
71
71
  A global can also be placed by hand. The starter lists Quote in its body zones, so the picker's *Global* group offers the site quote there, and an editor may still add a one-off Quote from the sections list.
72
72
 
@@ -20,7 +20,7 @@ Editors sign in at smoodly.io, never on your site: `/admin` sends them there and
20
20
 
21
21
  ## Who is editing what
22
22
 
23
- A lock after a title in Pages, in a collection or in Sections means someone else has that item open in the language you are looking at. Hover it for their name. Opening the item anyway shows a line under the top bar: *aleksi is also editing this page.* Nothing is locked; it is a heads-up.
23
+ A lock after a title in Pages, in a collection or in Globals means someone else has that item open in the language you are looking at. Hover it for their name. Opening the item anyway shows a line under the top bar: *aleksi is also editing this page.* Nothing is locked; it is a heads-up.
24
24
 
25
25
  Presence runs on Smoodly's service, over one connection per open admin tab. Without it the admin works exactly the same, minus the locks and the line; a tab that loses its connection reconnects on its own.
26
26
 
@@ -45,7 +45,7 @@ Register the list in the config's `globals`. A visual global's section must be i
45
45
 
46
46
  ## In the admin
47
47
 
48
- *Sections*, under ⋮ in the admin's top bar, lists every global with its kind, *Visual* or *Fields*, and its status in the selected language. A *Fields* global opens a form like an entry's, the fields in schema order. A *Visual* global opens on a canvas: a page of the site that shows it, the home page for a `layout` global and otherwise the first page that places it, with the global selected and the rest of the page dimmed. Its fields sit beside the canvas under *Content* and *Settings*, text marked for inline editing can be typed in place, and when several pages place it, *Shown on* picks the page. A visual global no page shows yet opens as a form. Either way the page autosaves, and carries the same *Publish* control, *History* and *Discard changes* as a page.
48
+ *Globals*, under ⋮ in the admin's top bar, lists every global with its kind, *Visual* or *Fields*, and its status in the selected language. A *Fields* global opens a form like an entry's, the fields in schema order. A *Visual* global opens on a canvas: a page of the site that shows it, the home page for a `layout` global and otherwise the first page that places it, with the global selected and the rest of the page dimmed. Its fields sit beside the canvas under *Content* and *Settings*, text marked for inline editing can be typed in place, and when several pages place it, *Shown on* picks the page. A visual global no page shows yet opens as a form. Either way the page autosaves, and carries the same *Publish* control, *History* and *Discard changes* as a page.
49
49
 
50
50
  A global exists in every language from the start, so there is nothing to add or delete. Publishing is per language, as for pages. A field's value is shared across languages unless it chains `.localized()`: the starter's contact email is one value, its address is translated.
51
51
 
@@ -112,6 +112,6 @@ On a single-language site there are no links and the header shows the brand and
112
112
 
113
113
  ## Editing them
114
114
 
115
- Editors change the header and footer under *Sections*, or in any page's canvas: both are selectable there, tagged *global*, with their own form and their own *Publish* in the inspector. The header's navigation edits in place like any list with [marks](../content-modeling/marks.md). Publishing a page never publishes the footer, because a footer going live on every page is a deliberate click.
115
+ Editors change the header and footer under *Globals*, or in any page's canvas: both are selectable there, tagged *global*, with their own form and their own *Publish* in the inspector. The header's navigation edits in place like any list with [marks](../content-modeling/marks.md). Publishing a page never publishes the footer, because a footer going live on every page is a deliberate click.
116
116
 
117
117
  Next, [The media library](../media/library.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoodly",
3
- "version": "0.0.62",
3
+ "version": "0.0.63",
4
4
  "description": "Open-source, code-first CMS for Astro and Next.js: define a component once, it becomes a typed, editable building block.",
5
5
  "license": "MIT",
6
6
  "engines": {