diff --git a/packages/core/src/design-jsx/reference/authoring.md b/packages/core/src/design-jsx/reference/authoring.md
index ba139ff0c..89bf846a2 100644
--- a/packages/core/src/design-jsx/reference/authoring.md
+++ b/packages/core/src/design-jsx/reference/authoring.md
@@ -26,8 +26,8 @@ This reference describes scene creation, not React DOM output. Use the `render`
## Variables and components
- Create document variables before referencing them with `designVar('id-or-name')`. `defineVars` groups references; it does not create variable collections.
-- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`; grid gaps and wrapped `rowGap` also support them.
-- References preserve real graph bindings, not just copied values. Initial scalar layout resolves the parent's inherited collection mode. Missing or incorrectly typed scalar variables are errors.
+- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`. Grid `columnGap`/`rowGap` and wrapped flex `rowGap` also support FLOAT references; grid `gap` overrides both axis-specific gaps. Use numbers or FLOAT references for these scalar props, not CSS unit strings.
+- References preserve real graph bindings, not just copied values. Set the intended collection mode on the parent before creating scalar-bound content: initial scalar layout resolves that inherited mode. This does not guarantee automatic scalar layout recomputation after a later mode switch. Verify resulting geometry as well as paint when changing modes. Missing or incorrectly typed scalar variables are errors.
- `bind` maps supported scene-field paths to variable IDs or references when no shorthand exists. Use semantic tokens consistently rather than declaring unused collections.
- A reusable JavaScript function shares source code, not component identity. Use `Component`, `ComponentSet`, and `Instance` for editable main components and linked instances.
- `Instance` resolves an existing component through `of`, `component`, or `componentId`. Component-set children named `variant=Primary`, for example, define variants that can be selected when instantiating the set.
@@ -35,6 +35,7 @@ This reference describes scene creation, not React DOM output. Use the `render`
- Child `propertyRefs` connect fields to stable property IDs, for example `[{ propertyId: 'message', field: 'TEXT' }]`. Supported fields are `TEXT`, `VISIBLE`, and `INSTANCE_SWAP`; text and swap references require text and instance nodes respectively. References do not depend on layer names.
- Instance assignments use the native string values (including `'true'` / `'false'` for BOOLEAN properties and component IDs for swaps). For example `Instance({ of: noteId, properties: { message: 'Updated review' } })`. Assignments persist through component synchronization; unknown IDs and invalid values fail rather than silently creating inert overrides. Select variants through component-set variant props, not through instance property assignments.
- Reuse existing local or library components before recreating them. Keep meaningful text, visibility, and swap properties exposed rather than hand-editing cloned child nodes.
+- Distinguish a main component's default size from its instance's placement constraints. Verify the actual instance bounds in narrower parents; a requested Fill dimension alone is not proof that inherited sizing changed. Do not compensate for a sizing mismatch with guessed heights, clipping, or manually positioned siblings.
## Verification
diff --git a/packages/core/src/design-jsx/reference/examples.ts b/packages/core/src/design-jsx/reference/examples.ts
index dc879ef7f..677f6d96e 100644
--- a/packages/core/src/design-jsx/reference/examples.ts
+++ b/packages/core/src/design-jsx/reference/examples.ts
@@ -17,7 +17,7 @@ export const AUTHORING_EXAMPLES: readonly AuthoringExample[] = [
{
title: 'Variable-bound spacing and typography',
jsx: dedent`
- A note that grows with its content.
+ A note that grows with its content.
`
}
]
diff --git a/packages/docs/reference/design-authoring.md b/packages/docs/reference/design-authoring.md
index b6af7e3ce..ca61b2d0e 100644
--- a/packages/docs/reference/design-authoring.md
+++ b/packages/docs/reference/design-authoring.md
@@ -28,8 +28,8 @@ This reference describes scene creation, not React DOM output. Use the `render`
## Variables and components
- Create document variables before referencing them with `designVar('id-or-name')`. `defineVars` groups references; it does not create variable collections.
-- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`; grid gaps and wrapped `rowGap` also support them.
-- References preserve real graph bindings, not just copied values. Initial scalar layout resolves the parent's inherited collection mode. Missing or incorrectly typed scalar variables are errors.
+- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`. Grid `columnGap`/`rowGap` and wrapped flex `rowGap` also support FLOAT references; grid `gap` overrides both axis-specific gaps. Use numbers or FLOAT references for these scalar props, not CSS unit strings.
+- References preserve real graph bindings, not just copied values. Set the intended collection mode on the parent before creating scalar-bound content: initial scalar layout resolves that inherited mode. This does not guarantee automatic scalar layout recomputation after a later mode switch. Verify resulting geometry as well as paint when changing modes. Missing or incorrectly typed scalar variables are errors.
- `bind` maps supported scene-field paths to variable IDs or references when no shorthand exists. Use semantic tokens consistently rather than declaring unused collections.
- A reusable JavaScript function shares source code, not component identity. Use `Component`, `ComponentSet`, and `Instance` for editable main components and linked instances.
- `Instance` resolves an existing component through `of`, `component`, or `componentId`. Component-set children named `variant=Primary`, for example, define variants that can be selected when instantiating the set.
@@ -37,6 +37,7 @@ This reference describes scene creation, not React DOM output. Use the `render`
- Child `propertyRefs` connect fields to stable property IDs, for example `[{ propertyId: 'message', field: 'TEXT' }]`. Supported fields are `TEXT`, `VISIBLE`, and `INSTANCE_SWAP`; text and swap references require text and instance nodes respectively. References do not depend on layer names.
- Instance assignments use the native string values (including `'true'` / `'false'` for BOOLEAN properties and component IDs for swaps). For example `Instance({ of: noteId, properties: { message: 'Updated review' } })`. Assignments persist through component synchronization; unknown IDs and invalid values fail rather than silently creating inert overrides. Select variants through component-set variant props, not through instance property assignments.
- Reuse existing local or library components before recreating them. Keep meaningful text, visibility, and swap properties exposed rather than hand-editing cloned child nodes.
+- Distinguish a main component's default size from its instance's placement constraints. Verify the actual instance bounds in narrower parents; a requested Fill dimension alone is not proof that inherited sizing changed. Do not compensate for a sizing mismatch with guessed heights, clipping, or manually positioned siblings.
## Verification
@@ -57,7 +58,7 @@ The examples below are executed by the authoring-reference tests. Create the nam
```jsx
-A note that grows with its content.
+A note that grows with its content.
```
diff --git a/skills/open-pencil/references/design-authoring.md b/skills/open-pencil/references/design-authoring.md
index b6af7e3ce..ca61b2d0e 100644
--- a/skills/open-pencil/references/design-authoring.md
+++ b/skills/open-pencil/references/design-authoring.md
@@ -28,8 +28,8 @@ This reference describes scene creation, not React DOM output. Use the `render`
## Variables and components
- Create document variables before referencing them with `designVar('id-or-name')`. `defineVars` groups references; it does not create variable collections.
-- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`; grid gaps and wrapped `rowGap` also support them.
-- References preserve real graph bindings, not just copied values. Initial scalar layout resolves the parent's inherited collection mode. Missing or incorrectly typed scalar variables are errors.
+- COLOR references work in paint props. FLOAT references work in `w`, `h`, `gap`, padding, corner radii, `strokeWidth`, `opacity`, text `size`/`fontSize`, `lineHeight`, and `letterSpacing`. Grid `columnGap`/`rowGap` and wrapped flex `rowGap` also support FLOAT references; grid `gap` overrides both axis-specific gaps. Use numbers or FLOAT references for these scalar props, not CSS unit strings.
+- References preserve real graph bindings, not just copied values. Set the intended collection mode on the parent before creating scalar-bound content: initial scalar layout resolves that inherited mode. This does not guarantee automatic scalar layout recomputation after a later mode switch. Verify resulting geometry as well as paint when changing modes. Missing or incorrectly typed scalar variables are errors.
- `bind` maps supported scene-field paths to variable IDs or references when no shorthand exists. Use semantic tokens consistently rather than declaring unused collections.
- A reusable JavaScript function shares source code, not component identity. Use `Component`, `ComponentSet`, and `Instance` for editable main components and linked instances.
- `Instance` resolves an existing component through `of`, `component`, or `componentId`. Component-set children named `variant=Primary`, for example, define variants that can be selected when instantiating the set.
@@ -37,6 +37,7 @@ This reference describes scene creation, not React DOM output. Use the `render`
- Child `propertyRefs` connect fields to stable property IDs, for example `[{ propertyId: 'message', field: 'TEXT' }]`. Supported fields are `TEXT`, `VISIBLE`, and `INSTANCE_SWAP`; text and swap references require text and instance nodes respectively. References do not depend on layer names.
- Instance assignments use the native string values (including `'true'` / `'false'` for BOOLEAN properties and component IDs for swaps). For example `Instance({ of: noteId, properties: { message: 'Updated review' } })`. Assignments persist through component synchronization; unknown IDs and invalid values fail rather than silently creating inert overrides. Select variants through component-set variant props, not through instance property assignments.
- Reuse existing local or library components before recreating them. Keep meaningful text, visibility, and swap properties exposed rather than hand-editing cloned child nodes.
+- Distinguish a main component's default size from its instance's placement constraints. Verify the actual instance bounds in narrower parents; a requested Fill dimension alone is not proof that inherited sizing changed. Do not compensate for a sizing mismatch with guessed heights, clipping, or manually positioned siblings.
## Verification
@@ -57,7 +58,7 @@ The examples below are executed by the authoring-reference tests. Create the nam
```jsx
-A note that grows with its content.
+A note that grows with its content.
```
diff --git a/tests/engine/render/jsx/reference.test.ts b/tests/engine/render/jsx/reference.test.ts
index 27fee3829..ce3db821e 100644
--- a/tests/engine/render/jsx/reference.test.ts
+++ b/tests/engine/render/jsx/reference.test.ts
@@ -12,31 +12,41 @@ import SYSTEM_PROMPT from '@/app/ai/chat/system-prompt'
import { getNodeOrThrow } from '#tests/helpers/assert'
import { makeSceneGraph } from '#tests/helpers/scene'
+function exampleGraph() {
+ const graph = makeSceneGraph()
+ graph.addCollection({
+ id: 'tokens',
+ name: 'Tokens',
+ modes: [
+ { modeId: 'default', name: 'Default' },
+ { modeId: 'compact', name: 'Compact' }
+ ],
+ defaultModeId: 'default',
+ variableIds: []
+ })
+ for (const [name, value, compact] of [
+ ['Space/small', 8, 4],
+ ['Space/medium', 16, 12],
+ ['Type/body', 12, 11],
+ ['Type/body-leading', 20, 16],
+ ['Type/body-tracking', 0.2, 0]
+ ] as const) {
+ graph.addVariable({
+ id: name,
+ name,
+ type: 'FLOAT',
+ collectionId: 'tokens',
+ valuesByMode: { default: value, compact },
+ description: '',
+ hiddenFromPublishing: false
+ })
+ }
+ return graph
+}
+
for (const example of AUTHORING_EXAMPLES) {
test(`shared authoring example: ${example.title}`, async () => {
- const graph = makeSceneGraph()
- graph.addCollection({
- id: 'tokens',
- name: 'Tokens',
- modes: [{ modeId: 'default', name: 'Default' }],
- defaultModeId: 'default',
- variableIds: []
- })
- for (const [name, value] of [
- ['Space/small', 8],
- ['Space/medium', 16],
- ['Type/body', 12]
- ] as const) {
- graph.addVariable({
- id: name,
- name,
- type: 'FLOAT',
- collectionId: 'tokens',
- valuesByMode: { default: value },
- description: '',
- hiddenFromPublishing: false
- })
- }
+ const graph = exampleGraph()
const [result] = await renderJSX(graph, example.jsx)
const frame = getNodeOrThrow(graph, result.id)
expect(frame.width).toBe(280)
@@ -47,6 +57,36 @@ for (const example of AUTHORING_EXAMPLES) {
})
}
+test('shared bound example retains bindings and resolves the initial inherited mode', async () => {
+ const graph = exampleGraph()
+ const parent = graph.createNode('FRAME', graph.getPages()[0].id, {
+ variableModes: { tokens: 'compact' }
+ })
+ for (const example of AUTHORING_EXAMPLES) {
+ await renderJSX(graph, example.jsx, { parentId: parent.id })
+ }
+ const bound = graph.getChildren(parent.id).find((node) => node.boundVariables.itemSpacing)
+ if (!bound) throw new Error('Expected a variable-bound authoring example')
+ expect(bound.itemSpacing).toBe(4)
+ expect(bound.paddingTop).toBe(12)
+ expect(bound.boundVariables).toMatchObject({
+ itemSpacing: 'Space/small',
+ paddingTop: 'Space/medium',
+ paddingRight: 'Space/medium',
+ paddingBottom: 'Space/medium',
+ paddingLeft: 'Space/medium'
+ })
+ const [text] = graph.getChildren(bound.id)
+ expect(text.fontSize).toBe(11)
+ expect(text.lineHeight).toBe(16)
+ expect(text.letterSpacing).toBe(0)
+ expect(text.boundVariables).toMatchObject({
+ fontSize: 'Type/body',
+ lineHeight: 'Type/body-leading',
+ letterSpacing: 'Type/body-tracking'
+ })
+})
+
test('public exports and runtime prompts use the same authoring reference', () => {
expect(BARREL_REFERENCE).toBe(JSX_REFERENCE)
expect(BARREL_CODEGEN).toBe(CODEGEN_PROMPT)