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.
- package/dist/core/createApp.d.ts +4 -103
- package/dist/core/createApp.js +3 -24
- package/dist/core/createBlock.d.ts +2 -2
- package/dist/core/createBlock.js +41 -185
- package/dist/core/createLayout.d.ts +6 -77
- package/dist/core/createLayout.js +12 -50
- package/dist/core/editing/createEditableBlock.d.ts +9 -75
- package/dist/core/editing/createEditableBlock.js +258 -640
- package/dist/core/hooks/useIsEditable.js +2 -2
- package/dist/core/lib/artifactData.js +8 -3
- package/dist/core/lib/contentType.d.ts +23 -38
- package/dist/core/lib/contentType.js +34 -62
- package/dist/core/lib/iconTypes.d.ts +0 -3
- package/dist/core/lib/icons.js +63 -51
- package/dist/core/lib/recordScope.js +1 -1
- package/dist/features/canvas/CanvasOverlays.js +94 -176
- package/dist/features/canvas/CanvasPageFrame.js +62 -68
- package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -3
- package/dist/features/canvas/canvasOverlayGeometry.js +1 -7
- package/dist/features/canvas/sidebar/CuratedPageInspector.js +3 -21
- package/dist/features/canvas/sidebar/LayoutPageBlocks.js +1 -2
- package/dist/features/canvas/useCanvasCamera.js +1 -1
- package/dist/features/canvas/useCanvasFrame.js +1 -1
- package/dist/features/content/ContentCollection.js +2 -2
- package/dist/features/content/collection-form.js +6 -7
- package/dist/features/page/CuratedPageContent.js +15 -24
- package/dist/features/page/UnplacedBlockContent.js +1 -6
- package/dist/features/preview/CamoxPreview.js +30 -33
- package/dist/features/preview/EditablePageContent.js +131 -151
- package/dist/features/preview/components/BlockActionsPopover.js +43 -152
- package/dist/features/preview/components/CanvasPreview.js +2 -2
- package/dist/features/preview/components/Frame.js +3 -3
- package/dist/features/preview/components/IconFieldEditor.js +1 -1
- package/dist/features/preview/components/ItemFieldsEditor.js +1 -3
- package/dist/features/preview/components/Overlays.js +18 -84
- package/dist/features/preview/components/PageTree.js +57 -103
- package/dist/features/preview/components/PreviewPanel.js +8 -11
- package/dist/features/preview/components/RecordCombobox.js +65 -78
- package/dist/features/preview/components/ReferenceFieldEditor.js +1 -19
- package/dist/features/preview/components/ReferenceListFieldEditor.js +1 -19
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +2 -2
- package/dist/features/preview/components/SelectionEditor.js +5 -4
- package/dist/features/preview/components/useRepeatableItemActions.js +18 -24
- package/dist/features/preview/components/useUpdateBlockPosition.js +1 -1
- package/dist/features/preview/overlayMessages.js +1 -4
- package/dist/features/preview/previewStore.js +3 -23
- package/dist/features/renderer/RendererApp.js +17 -141
- package/dist/features/renderer/definitionAnswers.js +1 -8
- package/dist/features/renderer/rendererHost.js +1 -31
- package/dist/features/renderer/studioConnection.js +1 -25
- package/dist/features/runtime/pageApp.js +1 -1
- package/dist/features/runtime/runtime.js +3 -3
- package/dist/features/studio/components/ThreadMessages.js +89 -22
- package/dist/features/vite/blockBoilerplate.js +1 -1
- package/dist/features/vite/definitionsSync.js +1 -8
- package/dist/lib/queries.js +0 -1
- package/dist/lib/studio-definitions.js +0 -2
- package/dist/lib/studio-reads.js +0 -5
- package/dist/lib/use-thread.js +196 -138
- package/dist/package.js +1 -1
- package/dist/studio-overlays.css +0 -29
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/skills/camox/SKILL.md +1 -1
- package/skills/camox/references/artifacts.md +4 -4
- package/skills/camox/references/block-definitions.md +21 -25
- package/skills/camox/references/dom-integrations.md +1 -1
- package/skills/camox/references/layout-definitions.md +3 -12
- package/skills/camox/references/routed-layouts.md +2 -2
- package/skills/camox-expand/SKILL.md +5 -5
- package/skills/camoxify/SKILL.md +4 -4
- package/dist/features/canvas/canvasInsertionSeams.js +0 -44
- package/dist/features/preview/components/AddBlockSheet.js +0 -314
- package/dist/features/preview/components/BlockPeek.js +0 -66
- package/dist/features/preview/components/BlockThumbnail.js +0 -281
- package/dist/features/preview/components/blockGridNavigation.js +0 -15
- 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.
|
|
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/
|
|
152
|
-
"@camox/
|
|
153
|
-
"@camox/
|
|
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",
|
package/skills/camox/SKILL.md
CHANGED
|
@@ -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
|
|
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({
|
|
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
|
-
-
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
147
|
+
Declare an icon field, then render inline SVG:
|
|
147
148
|
|
|
148
149
|
```tsx
|
|
149
150
|
// In content:
|
|
150
|
-
icon: field.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
|
|
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
|
|
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.
|
|
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({
|
|
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({
|
|
194
|
+
field.imageList({ title: "Gallery images" });
|
|
198
195
|
field.file({ accept: ["application/pdf"], title: "PDF Document" });
|
|
199
|
-
field.fileList({ accept: ["application/pdf"],
|
|
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.
|
|
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
|
|
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({
|
|
231
|
-
description: field.string({
|
|
226
|
+
name: field.string({ title: "Name" }),
|
|
227
|
+
description: field.string({ title: "Description" }),
|
|
232
228
|
}),
|
|
233
229
|
settings: (setting) => ({
|
|
234
|
-
highlighted: setting.boolean({
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
55
|
-
- **New Settings on existing Block Definitions
|
|
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
|
|
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
|
|
70
|
-
- every existing block that gained a Setting renders the same as its capture with
|
|
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.
|
package/skills/camoxify/SKILL.md
CHANGED
|
@@ -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,
|
|
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
|
|
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.
|
|
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 };
|