camox 0.71.0 → 0.72.0

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 (77) hide show
  1. package/dist/core/createApp.d.ts +4 -103
  2. package/dist/core/createApp.js +3 -24
  3. package/dist/core/createBlock.d.ts +2 -2
  4. package/dist/core/createBlock.js +41 -185
  5. package/dist/core/createLayout.d.ts +6 -77
  6. package/dist/core/createLayout.js +12 -50
  7. package/dist/core/editing/createEditableBlock.d.ts +9 -75
  8. package/dist/core/editing/createEditableBlock.js +258 -640
  9. package/dist/core/hooks/useIsEditable.js +2 -2
  10. package/dist/core/lib/artifactData.js +8 -3
  11. package/dist/core/lib/contentType.d.ts +23 -38
  12. package/dist/core/lib/contentType.js +34 -62
  13. package/dist/core/lib/iconTypes.d.ts +0 -3
  14. package/dist/core/lib/icons.js +63 -51
  15. package/dist/core/lib/recordScope.js +1 -1
  16. package/dist/features/canvas/CanvasOverlays.js +94 -176
  17. package/dist/features/canvas/CanvasPageFrame.js +62 -68
  18. package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -3
  19. package/dist/features/canvas/canvasOverlayGeometry.js +1 -7
  20. package/dist/features/canvas/sidebar/CuratedPageInspector.js +3 -21
  21. package/dist/features/canvas/sidebar/LayoutPageBlocks.js +1 -2
  22. package/dist/features/canvas/useCanvasCamera.js +1 -1
  23. package/dist/features/canvas/useCanvasFrame.js +1 -1
  24. package/dist/features/content/ContentCollection.js +2 -2
  25. package/dist/features/content/collection-form.js +6 -7
  26. package/dist/features/page/CuratedPageContent.js +15 -24
  27. package/dist/features/page/UnplacedBlockContent.js +1 -6
  28. package/dist/features/preview/CamoxPreview.js +30 -33
  29. package/dist/features/preview/EditablePageContent.js +131 -151
  30. package/dist/features/preview/components/BlockActionsPopover.js +43 -152
  31. package/dist/features/preview/components/CanvasPreview.js +2 -2
  32. package/dist/features/preview/components/Frame.js +3 -3
  33. package/dist/features/preview/components/IconFieldEditor.js +1 -1
  34. package/dist/features/preview/components/ItemFieldsEditor.js +1 -3
  35. package/dist/features/preview/components/Overlays.js +18 -84
  36. package/dist/features/preview/components/PageTree.js +57 -103
  37. package/dist/features/preview/components/PreviewPanel.js +8 -11
  38. package/dist/features/preview/components/RecordCombobox.js +65 -78
  39. package/dist/features/preview/components/ReferenceFieldEditor.js +1 -19
  40. package/dist/features/preview/components/ReferenceListFieldEditor.js +1 -19
  41. package/dist/features/preview/components/RemovedBlockPlaceholder.js +2 -2
  42. package/dist/features/preview/components/SelectionEditor.js +5 -4
  43. package/dist/features/preview/components/useRepeatableItemActions.js +18 -24
  44. package/dist/features/preview/components/useUpdateBlockPosition.js +1 -1
  45. package/dist/features/preview/overlayMessages.js +1 -4
  46. package/dist/features/preview/previewStore.js +3 -23
  47. package/dist/features/renderer/RendererApp.js +17 -141
  48. package/dist/features/renderer/definitionAnswers.js +1 -8
  49. package/dist/features/renderer/rendererHost.js +1 -31
  50. package/dist/features/renderer/studioConnection.js +1 -25
  51. package/dist/features/runtime/pageApp.js +1 -1
  52. package/dist/features/runtime/runtime.js +3 -3
  53. package/dist/features/studio/components/ThreadMessages.js +89 -22
  54. package/dist/features/vite/blockBoilerplate.js +1 -1
  55. package/dist/features/vite/definitionsSync.js +1 -8
  56. package/dist/lib/queries.js +0 -1
  57. package/dist/lib/studio-definitions.js +0 -2
  58. package/dist/lib/studio-reads.js +0 -5
  59. package/dist/lib/use-thread.js +196 -138
  60. package/dist/package.js +1 -1
  61. package/dist/studio-overlays.css +0 -29
  62. package/dist/studio.css +1 -1
  63. package/package.json +4 -4
  64. package/skills/camox/SKILL.md +1 -1
  65. package/skills/camox/references/artifacts.md +4 -4
  66. package/skills/camox/references/block-definitions.md +21 -25
  67. package/skills/camox/references/dom-integrations.md +1 -1
  68. package/skills/camox/references/layout-definitions.md +3 -12
  69. package/skills/camox/references/routed-layouts.md +2 -2
  70. package/skills/camox-expand/SKILL.md +5 -5
  71. package/skills/camoxify/SKILL.md +4 -4
  72. package/dist/features/canvas/canvasInsertionSeams.js +0 -44
  73. package/dist/features/preview/components/AddBlockSheet.js +0 -314
  74. package/dist/features/preview/components/BlockPeek.js +0 -66
  75. package/dist/features/preview/components/BlockThumbnail.js +0 -281
  76. package/dist/features/preview/components/blockGridNavigation.js +0 -15
  77. package/dist/features/preview/referencePickerFocus.js +0 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camox",
3
- "version": "0.71.0",
3
+ "version": "0.72.0",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "camox": "./bin/camox.mjs"
@@ -148,9 +148,9 @@
148
148
  "unhead": "^3.4.3",
149
149
  "yaml": "^2.9.0",
150
150
  "zod": "^4.6.7",
151
- "@camox/api-contract": "0.71.0",
152
- "@camox/cli": "0.71.0",
153
- "@camox/ui": "0.71.0"
151
+ "@camox/ui": "0.72.0",
152
+ "@camox/api-contract": "0.72.0",
153
+ "@camox/cli": "0.72.0"
154
154
  },
155
155
  "devDependencies": {
156
156
  "@earendil-works/pi-ai": "1.3.0",
@@ -45,6 +45,6 @@ Use the platform. Build tabs, dialogs, carousels, menus and disclosures from nat
45
45
 
46
46
  ## Content
47
47
 
48
- Code definitions do not place blocks on existing Pages or assign their layouts; `blocks.initial` only seeds the initial homepage. When the request includes placement or assignment on existing Pages, make it as a content change once Camox has discovered the new definition. Singleton pages and derived routes are declared in code, never created as Pages.
48
+ Code definitions do not place blocks on Pages, assign layouts, or write content. When the request includes placement, assignment, or content, make it as a content change once Camox has discovered the new definition. Singleton pages and derived routes are declared in code, never created as Pages.
49
49
 
50
50
  A request can cross boundaries. Load an additional reference only once that need is established.
@@ -12,23 +12,23 @@ A definition that would fit one place on one site, or settings bolted on only to
12
12
 
13
13
  ```tsx
14
14
  content: (field) => ({
15
- heading: field.string({ default: "How it works" }),
15
+ heading: field.string({ title: "Heading" }),
16
16
  visual: field.artifact({ size: "fill", title: "Diagram" }),
17
17
  }),
18
18
  ```
19
19
 
20
20
  - `size` (required): `"fill"` fills a container whose size the block controls, for example with an aspect ratio; `"content"` is for frames whose height follows their page, such as calculators. See [Size to the content](#size-to-the-content).
21
21
  - `data` (optional): sibling content fields and settings shared with the artifact as **Artifact Data**, so editors can change what it shows without touching its source. Inside a repeater item, the siblings are the item's own fields and settings. The names are type-checked; artifacts, repeaters and references can't be listed. See [`camox.data`](#camoxdata).
22
- - `default` (optional): a starting source. Leave it out; agents write sources as content. An empty artifact renders nothing on the website and a placeholder in the studio preview.
22
+ - Agents write sources as content, never in the definition. An empty artifact renders nothing on the website and a placeholder in the studio preview.
23
23
  - Artifacts work in block content and repeater items, so also in layout and synced blocks. Collection records can't hold them.
24
24
 
25
25
  ```tsx
26
26
  content: (field) => ({
27
- label: field.string({ default: "Uptime" }),
27
+ label: field.string({ title: "Label" }),
28
28
  gauge: field.artifact({ size: "fill", data: ["label", "tone"] }),
29
29
  }),
30
30
  settings: (setting) => ({
31
- tone: setting.enum({ default: "calm", options: { calm: "Calm", bold: "Bold" } }),
31
+ tone: setting.enum({ options: { calm: "Calm", bold: "Bold" } }),
32
32
  }),
33
33
  ```
34
34
 
@@ -16,7 +16,7 @@ const myBlock = createBlock({
16
16
  title: "My Block",
17
17
  description: "A page introduction with an editable heading.",
18
18
  content: (field) => ({
19
- title: field.string({ default: "Welcome" }),
19
+ title: field.string({ title: "Title" }),
20
20
  }),
21
21
  toReact: MyBlockComponent,
22
22
  toTitle: ({ content }) => content.title,
@@ -45,7 +45,7 @@ For DOM listeners, scrolling, scripts, widgets, or JavaScript-driven animation,
45
45
  | `description` | yes | Agent guidance on placement, expected content, and tone. |
46
46
  | `content` | yes | `(field) => ({ ... })`: field names mapped to `field.*` builders for editable content. |
47
47
  | `settings` | no | `(setting) => ({ ... })`: settings-panel options; only `setting.enum` and `setting.boolean`. |
48
- | `layoutOnly` | no | If true, restricts placement to layouts and hides the type from the add-block picker. |
48
+ | `layoutOnly` | no | If true, restricts placement to layouts. |
49
49
  | `synced` | no | Defaults to false. Shares content/settings across placements; see below. |
50
50
  | `toReact` | yes | Named React function component. Receives `{ content, settings }` as props. |
51
51
  | `toTitle` | yes | `({ content, settings }) => ...` one-line title; see below. |
@@ -58,7 +58,7 @@ For DOM listeners, scrolling, scripts, widgets, or JavaScript-driven animation,
58
58
 
59
59
  For shared navbar/footer data across layouts, use `layoutOnly: true, synced: true`. These flags are independent: synced blocks can also be ordinary page-content blocks.
60
60
 
61
- - Editing any placement updates the shared content everywhere. New placements reuse existing data rather than resetting to defaults.
61
+ - Editing any placement updates the shared content everywhere. New placements reuse the shared content.
62
62
  - Position, layout slot, and deletion remain per placement; deleting one placement leaves the others.
63
63
  - Enabling syncing on an existing type applies the oldest placement's data to all placements. Disabling it leaves independently editable copies.
64
64
 
@@ -119,7 +119,9 @@ Only Boolean/Enum settings support these conditionals. A repeater's `settings` r
119
119
 
120
120
  ## Content Field Types
121
121
 
122
- `content` and a repeater's `content` receive a `field` builder; `settings` receive a `setting` builder. Each builder only offers what its context accepts: `field` has no `enum` or `boolean`, and `setting` has nothing else. Supply defaults for scalar fields; images/files get automatic placeholders (empty images paint with the stylesheet's `--camox-placeholder` and `--camox-placeholder-ink`), lists use `defaultItems`, and repeaters use their item schemas and bounds.
122
+ `content` and a repeater's `content` receive a `field` builder; `settings` receive a `setting` builder. Each builder only offers what its context accepts: `field` has no `enum` or `boolean`, and `setting` has nothing else.
123
+
124
+ A definition declares a block's shape, never its content: the agent writes every field's content when it places the block. A field nobody has written reads as the empty value of its kind: `""` for strings, embeds, icons and artifacts, an empty link, `null` for an image, file or reference, no items for lists and repeaters, `false` for a boolean setting, and an enum setting's first option. So render well with any content, including empty optional fields: a short or long title, a missing subtitle, one repeatable item or many. An empty image paints Camox's placeholder with the stylesheet's `--camox-placeholder` and `--camox-placeholder-ink`; an empty file, image list or file list renders nothing.
123
125
 
124
126
  ### field.string
125
127
 
@@ -127,7 +129,6 @@ Inline-editable text supporting formatting and links while remaining a string. S
127
129
 
128
130
  ```tsx
129
131
  field.string({
130
- default: "Hello world",
131
132
  title: "Heading", // Optional label
132
133
  maxLength: 280, // Optional
133
134
  minLength: 1, // Optional
@@ -143,34 +144,33 @@ Choose one Iconify collection in Vite config:
143
144
  camox({ projectId: "01a15fff-627b-7fa1-938b-93ab659c1acc", icons: "lucide" });
144
145
  ```
145
146
 
146
- Declare a non-nullable icon with a required, prefixed default, then render inline SVG:
147
+ Declare an icon field, then render inline SVG:
147
148
 
148
149
  ```tsx
149
150
  // In content:
150
- icon: field.icon({ default: "lucide:zap", title: "Icon" })
151
+ icon: field.icon({ title: "Icon" })
151
152
 
152
153
  // In the component (also supported as <item.Icon> inside repeaters):
153
154
  <block.Icon name="icon" className="size-6 text-brand" />
154
155
  <block.Icon name="icon" aria-label="Fast" />
155
156
  ```
156
157
 
157
- Icons are decorative unless given `aria-label` or `aria-labelledby`. Clicking an icon in preview opens the searchable collection picker. Values remain namespaced strings; editors cannot select another collection or an empty value.
158
+ Icons are decorative unless given `aria-label` or `aria-labelledby`. Clicking an icon in preview opens the searchable collection picker. Values are namespaced strings such as `"lucide:zap"`; editors cannot select another collection or clear the icon.
158
159
 
159
160
  Run `pnpm exec camox typegen` **before TypeScript checking in CI**, for example `camox typegen && tsc --noEmit`. This resolves Vite config without starting a server. It generates `src/camox/icons.gen.d.ts`; ensure your tsconfig includes it. Dev and build regenerate it automatically. Generation requires network access to the pinned Iconify snapshot.
160
161
 
161
- Camox resolves selected SVGs from that same versioned snapshot and serves them through its API. Server rendering preloads selected/default SVGs into hydration data; no visitor requests to Iconify and no rebuild is needed when content changes. Only the editor fetches collection metadata and picker previews, not a whole SVG catalog.
162
+ Camox resolves selected SVGs from that same versioned snapshot and serves them through its API. Server rendering preloads selected SVGs into hydration data; no visitor requests to Iconify and no rebuild is needed when content changes. Only the editor fetches collection metadata and picker previews, not a whole SVG catalog.
162
163
 
163
164
  Collection licenses still apply. SVG responses retain author/license attribution. Include any copyright notices, license text, or attribution required by your chosen collection in your site's distributed notices; Iconify does not relicense the icons. Changing collections or migrating existing icon values is not supported in v1.
164
165
 
165
166
  ### setting.boolean and setting.enum
166
167
 
167
- Settings only: configuration choices are never block or repeater content. Enum defaults must match an option key.
168
+ Settings only: configuration choices are never block or repeater content. An unset enum reads as its first option, so list the most common or neutral one first. An unset boolean reads as `false`, so name it for what turning it on does (`showBackground`, not `hideBackground`).
168
169
 
169
170
  ```tsx
170
171
  settings: (setting) => ({
171
- showBackground: setting.boolean({ default: false, title: "Show background" }),
172
+ showBackground: setting.boolean({ title: "Show background" }),
172
173
  alignment: setting.enum({
173
- default: "left",
174
174
  options: { left: "Left", center: "Center", right: "Right" },
175
175
  title: "Alignment",
176
176
  }),
@@ -182,10 +182,7 @@ settings: (setting) => ({
182
182
  Text, destination, and new-tab toggle. Curated page links store page IDs; singleton links store fixed URLs using the existing `type: "external"` representation, which also supports site-relative URLs: `{ type: "external", href: "/pokedex", text: "Pokédex", newTab: false }`. Both page kinds appear in Studio's destination picker. Inline `[Pokédex](/pokedex)` links stay in the current tab.
183
183
 
184
184
  ```tsx
185
- field.link({
186
- default: { text: "Learn more", href: "/", newTab: false },
187
- title: "CTA",
188
- });
185
+ field.link({ title: "CTA" });
189
186
  ```
190
187
 
191
188
  ### Images and files
@@ -194,19 +191,18 @@ Files support MIME filtering. Render each type with its matching helper; asset l
194
191
 
195
192
  ```tsx
196
193
  field.image({ title: "Cover photo" });
197
- field.imageList({ defaultItems: 6, title: "Gallery images" });
194
+ field.imageList({ title: "Gallery images" });
198
195
  field.file({ accept: ["application/pdf"], title: "PDF Document" });
199
- field.fileList({ accept: ["application/pdf"], defaultItems: 0, title: "Documents" });
196
+ field.fileList({ accept: ["application/pdf"], title: "Documents" });
200
197
  ```
201
198
 
202
199
  ### field.embed
203
200
 
204
- A URL validated against a regex. A nonmatching default throws at definition time.
201
+ A URL validated against a regex. Content that doesn't match is rejected.
205
202
 
206
203
  ```tsx
207
204
  field.embed({
208
205
  pattern: "https:\\/\\/(www\\.)?(youtube\\.com|youtu\\.be)\\/.+",
209
- default: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
210
206
  title: "YouTube URL",
211
207
  });
212
208
  ```
@@ -222,16 +218,16 @@ field.artifact({ size: "fill", data: ["headline", "variant"] }); // shares sibli
222
218
 
223
219
  ### field.repeater
224
220
 
225
- Structured items with their own content, optional settings, and required `toTitle` and `toMarkdown`. `minItems` must be at least 1; repeaters cannot be empty. Nest repeaters in an item's `content` when needed.
221
+ Structured items with their own content, optional settings, and required `toTitle` and `toMarkdown`. `minItems` must be at least 1: editors can't remove the last item. A block whose repeater was never written has no items, so render well with none. Nest repeaters in an item's `content` when needed.
226
222
 
227
223
  ```tsx
228
224
  field.repeater({
229
225
  content: (field) => ({
230
- name: field.string({ default: "Feature" }),
231
- description: field.string({ default: "Description" }),
226
+ name: field.string({ title: "Name" }),
227
+ description: field.string({ title: "Description" }),
232
228
  }),
233
229
  settings: (setting) => ({
234
- highlighted: setting.boolean({ default: false, title: "Highlighted" }),
230
+ highlighted: setting.boolean({ title: "Highlighted" }),
235
231
  }),
236
232
  minItems: 1,
237
233
  maxItems: 10,
@@ -279,7 +275,7 @@ Place a repeater's token where its items' nodes go, such as an FAQ's questions:
279
275
 
280
276
  ```tsx
281
277
  items: field.repeater({
282
- content: (field) => ({ question: field.string({ default: "Why?" }), answer: field.string({ default: "Because" }) }),
278
+ content: (field) => ({ question: field.string({ title: "Question" }), answer: field.string({ title: "Answer" }) }),
283
279
  minItems: 1,
284
280
  maxItems: 20,
285
281
  toTitle: ({ content }) => content.question,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Use this reference for block DOM listeners, scrolling, browser scripts, and third-party widgets. It can be read independently of [Block Definitions](block-definitions.md); read that reference only if also changing the block schema or field rendering API.
4
4
 
5
- **Do not use component-global `window` or `document` to target a block's DOM.** Camox preview (including block thumbnails) renders blocks into an iframe through a React portal, but component code executes in the editor's JavaScript realm. Global scroll listeners observe the editor; scripts appended to the global document load there instead of beside your block.
5
+ **Do not use component-global `window` or `document` to target a block's DOM.** Camox preview renders blocks into an iframe through a React portal, but component code executes in the editor's JavaScript realm. Global scroll listeners observe the editor; scripts appended to the global document load there instead of beside your block.
6
6
 
7
7
  Use `getElementContext` from `camox/dom` inside an effect or event handler, passing a rendered element. It returns `{ window, document }` from the element's `ownerDocument` and `defaultView`, or `null` for a missing element or a document without a window. It never falls back to globals, requires no provider, and works in regular rendering too. Don't access DOM APIs during SSR.
8
8
 
@@ -23,7 +23,6 @@ const myLayout = createLayout({
23
23
  blocks: {
24
24
  before: [navbarBlock],
25
25
  after: [footerBlock],
26
- initial: [() => import("../blocks/hero")],
27
26
  },
28
27
  toReact: MyLayoutComponent,
29
28
  toTitle: ({ page, project }) => `${page.title} | ${project.name}`,
@@ -50,7 +49,7 @@ export { myLayout as layout };
50
49
  | `id` | yes | Unique kebab-case identifier matching the filename without extension. |
51
50
  | `title` | yes | CMS display name. |
52
51
  | `description` | yes | Guidance for users and agents on which pages suit this layout. |
53
- | `blocks` | yes | `before`, `after`, and optional `initial` arrays; see layout slot rules below. |
52
+ | `blocks` | yes | `before` and `after` arrays; see layout slot rules below. |
54
53
  | `toReact` | yes | Named function component receiving the context and `{ children }` as props. |
55
54
  | `toTitle` | yes | `(context) => string`: the head title, `og:title`, and the Markdown front matter's `title`. |
56
55
  | `toDescription` | yes | `(context) => string \| undefined`: the head description and `og:description`. May return `undefined`. |
@@ -68,20 +67,12 @@ Every `to*` option receives the same context object, and none receives another o
68
67
 
69
68
  Singleton and derived layouts also receive `data` and `params`, and record pages `content`; see [Routed layouts](routed-layouts.md#metadata-and-markdown).
70
69
 
71
- ## Layout Slots — `before`, `after`, and `initial`
70
+ ## Layout Slots — `before` and `after`
72
71
 
73
72
  - `before` and `after` require blocks with `layoutOnly: true`. Either array may be empty.
74
73
  - Render groups with `<myLayout.BeforeBlocks />` and `<myLayout.AfterBlocks />`, not individual blocks. Array order controls rendering order; edit the config to add or reorder blocks.
75
74
  - For identical navbar/footer content across layouts, also set `synced: true`. `layoutOnly` restricts a block to layout slots; it does not enable syncing. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for sharing and existing-placement behavior.
76
-
77
- ## Initial Blocks — `blocks.initial` (optional)
78
-
79
- `initial` seeds the homepage at project creation, using block defaults in array order. It does not populate existing Pages.
80
-
81
- - List each block as an import of its module, `() => import("../blocks/hero")`, never a statically imported block. Layouts load on every page, so a static import would ship the block to every visitor.
82
- - Accepts only blocks with `layoutOnly` omitted or `false`.
83
- - At most one layout per app may define `initial`; multiple definitions cause a startup error.
84
- - Without `initial`, Camox seeds an empty homepage using the first curated layout. Apps with only singleton/derived layouts do not seed a curated homepage.
75
+ - A slot's new blocks start with no content when the layout syncs. Fill them as a [content change](../SKILL.md#content) once Camox has discovered the layout.
85
76
 
86
77
  ## OG Image — `toImage` (optional)
87
78
 
@@ -53,7 +53,7 @@ function PokedexPage() {
53
53
  - `loader` is optional. Return `{ kind: "data", data }`, synchronously or asynchronously. `layout.useData()` infers and returns the unwrapped `data`, not the envelope. It runs server-side for document requests and client navigation; the entire envelope travels through JSON to hydration. Keep data JSON-serializable (no functions, symbols, BigInt, or cyclic objects). Without a loader, `useData()` returns `undefined`. The loader also receives `locale`, the page's locale tag; Camox renders its data untranslated (see [Localization](localization.md#loaders)).
54
54
  - Before/after blocks must be `layoutOnly: true`. Put editable headings, introductions, and other bespoke content in these blocks; keep structural application UI in the component. See [Block Definitions](block-definitions.md).
55
55
  - For a navbar or footer whose content must stay identical across different layouts, also set `synced: true` in its `createBlock` options. `layoutOnly: true` alone only restricts placement. See [Synced blocks](block-definitions.md#synced-blocks-synced-optional) for sharing behavior.
56
- - No `children` slot or `blocks.initial`: editors do not compose or create these pages.
56
+ - No `children` slot: editors do not compose or create these pages.
57
57
  - No curated page record is created. Never create, duplicate, move, or assign a singleton as a Page.
58
58
  - Studio lists singletons as directly navigable pages with block editing, without deletion or URL/layout controls. They also appear in link destination pickers (including inline text links), “Go to page” actions, and the runtime sitemap. Derived pages appear in the sitemap only when their layout declares `routes` (see [Route discovery](#route-discovery)).
59
59
  - Singleton links are saved as fixed internal URLs, not synthetic database page IDs: link fields use `{ type: "external", href: "/pokedex", text: "Pokédex", newTab: false }` (the existing URL representation), while inline text uses `[Pokédex](/pokedex)`. Renaming a code-owned URL requires updating links or adding a redirect. Curated-page links retain their page-ID references.
@@ -65,7 +65,7 @@ function PokedexPage() {
65
65
 
66
66
  ## Derived routes
67
67
 
68
- Use the same file-based form with `kind: "derived"`, for example `createLayout("pokemon.$name")`. Its required `loader: ({ params }) => ...` receives typed `params.name` and returns `{ kind: "data", data }`. Read its unwrapped data through `layout.useData()`. Use `throw notFound()` (imported from `camox/createLayout`) for a missing record; other loader errors remain server errors. Like singletons, derived routes have before/after blocks but no page-owned `children` or `blocks.initial`. Unlike singletons, curated URLs retain precedence over derived routes.
68
+ Use the same file-based form with `kind: "derived"`, for example `createLayout("pokemon.$name")`. Its required `loader: ({ params }) => ...` receives typed `params.name` and returns `{ kind: "data", data }`. Read its unwrapped data through `layout.useData()`. Use `throw notFound()` (imported from `camox/createLayout`) for a missing record; other loader errors remain server errors. Like singletons, derived routes have before/after blocks but no page-owned `children`. Unlike singletons, curated URLs retain precedence over derived routes.
69
69
 
70
70
  ### Route discovery
71
71
 
@@ -51,11 +51,11 @@ Stop suggesting when the next idea would be filler; there is no target count. Th
51
51
 
52
52
  Make only these kinds of change:
53
53
 
54
- - **New Block Definitions**, following [Block Definitions](../camox/references/block-definitions.md), [Localized code](../camox/SKILL.md#localized-code) (every visible text in fields, internal links through `useLocalizedHref` without a locale prefix), and Camoxify's rules on [styling](../camoxify/SKILL.md#use-the-existing-styling-stack), [genericity and default content](../camoxify/SKILL.md#keep-definitions-generic), [repeaters](../camoxify/SKILL.md#model-repetition-not-a-fixed-screenshot), [Settings](../camoxify/SKILL.md#use-settings-for-constrained-choices), and [formatted text](../camoxify/SKILL.md#keep-formatted-text-in-one-field).
55
- - **New Settings on existing Block Definitions**, with defaults that leave current rendering unchanged. Existing content schemas stay exactly as they are. Before editing the block, capture it at desktop and mobile on a Page that places it, on the running dev server; step 6 compares against this capture.
54
+ - **New Block Definitions**, following [Block Definitions](../camox/references/block-definitions.md), [Localized code](../camox/SKILL.md#localized-code) (every visible text in fields, internal links through `useLocalizedHref` without a locale prefix), and Camoxify's rules on [styling](../camoxify/SKILL.md#use-the-existing-styling-stack), [genericity](../camoxify/SKILL.md#keep-definitions-generic), [repeaters](../camoxify/SKILL.md#model-repetition-not-a-fixed-screenshot), [Settings](../camoxify/SKILL.md#use-settings-for-constrained-choices), and [formatted text](../camoxify/SKILL.md#keep-formatted-text-in-one-field).
55
+ - **New Settings on existing Block Definitions** whose unset value (`false`, or an enum's first option) leaves current rendering unchanged, since existing blocks read it. Existing content schemas stay exactly as they are. Before editing the block, capture it at desktop and mobile on a Page that places it, on the running dev server; step 6 compares against this capture.
56
56
  - **Description edits.** A new block's `description` says when to choose it over its siblings. Edit the siblings' descriptions so each one names what sets it apart, and the Agent can choose between them.
57
57
 
58
- New blocks live only in code, plus one review Page in the development draft: create it with the CLI (see [Managing Pages and Blocks](../camox-cli/references/cli-content.md)) and place each new block on it with its default content. Leave every other Page, layouts, and publishing alone. This rules out new Layout-only Blocks: a Page can't hold them, and they only sync once a layout uses them. An existing Layout-only Block that gains a Setting is captured and checked on a Page whose layout uses it.
58
+ New blocks live only in code, plus one review Page in the development draft: create it with the CLI (see [Managing Pages and Blocks](../camox-cli/references/cli-content.md)) and place each new block on it with representative content. Leave every other Page, layouts, and publishing alone. This rules out new Layout-only Blocks: a Page can't hold them, and they only sync once a layout uses them. An existing Layout-only Block that gains a Setting is captured and checked on a Page whose layout uses it.
59
59
 
60
60
  ## 6. Verify
61
61
 
@@ -66,5 +66,5 @@ The run is complete when:
66
66
  - every approved item is built, and every rejected one is deleted,
67
67
  - the review Page is deleted once the operator has reviewed it,
68
68
  - the project's `check` script passes, and so does `vp lint --type-aware --type-check`, since `vp check` skips type errors unless the project's Vite config sets `lint.options.typeCheck`,
69
- - every new block renders correctly with its default content at desktop and mobile on the review Page,
70
- - every existing block that gained a Setting renders the same as its capture with default Settings.
69
+ - every new block renders correctly with its review content, and with its optional fields empty, at desktop and mobile on the review Page,
70
+ - every existing block that gained a Setting renders the same as its capture with the new Setting unset.
@@ -47,9 +47,9 @@ Express layout, spacing, typography, colors, responsive behavior, and interactiv
47
47
 
48
48
  ## Keep definitions generic
49
49
 
50
- Block names, schemas, rendering, and **default content** must work on other pages. Use `feature-grid`, not `acme-homepage-features`. Defaults are neutral examples, not the reference's copy, links, assets, or item data.
50
+ Block names, schemas, and rendering must work on other pages. Use `feature-grid`, not `acme-homepage-features`.
51
51
 
52
- Apply reference-specific content **only to instances through the CLI**, never by changing definition defaults or hardcoding it in JSX.
52
+ Apply reference-specific content **only to instances through the CLI**, never in the definition or hardcoded in JSX.
53
53
 
54
54
  Keep the result translatable, following [Localized code](../camox/SKILL.md#localized-code): every visible text in the shell, navigation and footer included, lives in blocks, and internal links go through `useLocalizedHref`, written without a locale prefix.
55
55
 
@@ -63,7 +63,7 @@ Choose bounds from real constraints, not the number of items visible in the refe
63
63
 
64
64
  Before adding a `content` field, ask whether the editor is writing free-form content or choosing from known options. For configuration choices such as alignment, theme, size, or variant, use `settings` with `setting.enum`. For on/off choices such as showing a background or enabling autoplay, use `settings` with `setting.boolean`.
65
65
 
66
- Do not model these choices as `content` fields using `field.string`, even if rendering code could interpret strings such as `"left"`, `"dark"`, or `"true"`. Editors should choose a valid option or toggle a setting, not type a magic string. Reserve `content`'s `field.string` for genuinely free-form text, such as headings and descriptions. Enum defaults must match an option key.
66
+ Do not model these choices as `content` fields using `field.string`, even if rendering code could interpret strings such as `"left"`, `"dark"`, or `"true"`. Editors should choose a valid option or toggle a setting, not type a magic string. Reserve `content`'s `field.string` for genuinely free-form text, such as headings and descriptions.
67
67
 
68
68
  ## Keep formatted text in one field
69
69
 
@@ -81,7 +81,7 @@ Use inline formatting and `textStyle`, including highlights, instead of fields s
81
81
 
82
82
  Follow the [Camox CLI workflow](../camox-cli/SKILL.md) to assign layouts, place blocks, and populate content, including shared navigation/footer content. Don't publish unless asked.
83
83
 
84
- Compare the rendered site with the reference at desktop and mobile sizes. Verify that fields remain editable and that longer text and different repeater counts still work. Fix the reusable implementation, not just the one captured arrangement.
84
+ Compare the rendered site with the reference at desktop and mobile sizes. Verify that fields remain editable and that longer text, empty optional fields, and different repeater counts still work. Fix the reusable implementation, not just the one captured arrangement.
85
85
 
86
86
  In multi-page runs, give this full check to every page that introduced or changed a definition, and to each page family's representative page. Compare the remaining pages with their source at desktop size. The run is complete when every page in `camoxify-progress.md` is verified.
87
87
 
@@ -1,44 +0,0 @@
1
- //#region src/features/canvas/canvasInsertionSeams.ts
2
- /** Main block wrappers publish insertion permissions, including layout boundaries.
3
- * Detached block targets and fields deliberately do not publish this metadata. */
4
- function readBlockInsertion(element) {
5
- const value = element.getAttribute("data-camox-block-insertion");
6
- if (!value) return;
7
- try {
8
- const insertion = JSON.parse(value);
9
- if (!insertion || typeof insertion.position !== "string" || typeof insertion.before !== "boolean" || typeof insertion.after !== "boolean" || insertion.afterPosition !== void 0 && insertion.afterPosition !== null && typeof insertion.afterPosition !== "string") return;
10
- return insertion;
11
- } catch {
12
- return;
13
- }
14
- }
15
- /** Walk boundaries, not blocks: the bottom of one block and the top of its
16
- * successor describe ONE insertion seam, even when both permit insertion. */
17
- function canvasInsertionSeams(targets) {
18
- const blocks = targets.filter((target) => target.insertion && target.bounds.width > 0 && target.bounds.height > 0);
19
- const seams = [];
20
- for (let index = 0; index <= blocks.length; index++) {
21
- const previous = blocks[index - 1];
22
- const next = blocks[index];
23
- const anchor = previous?.insertion?.after ? previous : next?.insertion?.before ? next : null;
24
- if (!anchor?.insertion) continue;
25
- const insertPosition = anchor === previous ? "after" : "before";
26
- const { position, afterPosition } = anchor.insertion;
27
- const y = previous && next ? (previous.bounds.y + previous.bounds.height + next.bounds.y) / 2 : anchor.bounds.y + (insertPosition === "after" ? anchor.bounds.height : 0);
28
- seams.push({
29
- key: `${previous?.element.getAttribute("data-camox-block-id") ?? "start"}:${next?.element.getAttribute("data-camox-block-id") ?? "end"}`,
30
- x: anchor.bounds.x + anchor.bounds.width / 2,
31
- y,
32
- request: {
33
- type: "CAMOX_ADD_BLOCK_REQUEST",
34
- blockPosition: position,
35
- insertPosition,
36
- ...afterPosition !== void 0 ? { afterPosition } : {}
37
- }
38
- });
39
- }
40
- return seams;
41
- }
42
-
43
- //#endregion
44
- export { canvasInsertionSeams, readBlockInsertion };