From 8189c08e0bbeaf02ac4745e69ff432131fa9ac49 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sun, 13 Sep 2026 20:54:27 +0300 Subject: [PATCH] docs: clarify scalar modes and instance sizing guidance --- .../src/design-jsx/reference/authoring.md | 5 +- .../core/src/design-jsx/reference/examples.ts | 2 +- packages/docs/reference/design-authoring.md | 7 +- .../references/design-authoring.md | 7 +- tests/engine/render/jsx/reference.test.ts | 86 ++++++++++++++----- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/packages/core/src/design-jsx/reference/authoring.md b/packages/core/src/design-jsx/reference/authoring.md index 9cd9cd518..4a494763b 100644 --- a/packages/core/src/design-jsx/reference/authoring.md +++ b/packages/core/src/design-jsx/reference/authoring.md @@ -26,11 +26,12 @@ 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. +- 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. - Reuse existing local or library components before recreating them. Expose meaningful text, visibility, and swap properties through the existing component-property APIs; do not assume every native property API is already exposed as a JSX prop. ## 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 fe1516998..2dc9624eb 100644 --- a/packages/docs/reference/design-authoring.md +++ b/packages/docs/reference/design-authoring.md @@ -28,11 +28,12 @@ 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. +- 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. - Reuse existing local or library components before recreating them. Expose meaningful text, visibility, and swap properties through the existing component-property APIs; do not assume every native property API is already exposed as a JSX prop. ## Verification @@ -54,7 +55,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 fe1516998..2dc9624eb 100644 --- a/skills/open-pencil/references/design-authoring.md +++ b/skills/open-pencil/references/design-authoring.md @@ -28,11 +28,12 @@ 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. +- 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. - Reuse existing local or library components before recreating them. Expose meaningful text, visibility, and swap properties through the existing component-property APIs; do not assume every native property API is already exposed as a JSX prop. ## Verification @@ -54,7 +55,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)