Skip to content

fix: guard string component variable values - #480

Merged
ysamcode merged 1 commit into
developfrom
fix/guard-string-component-variable-values
Jul 30, 2026
Merged

fix: guard string component variable values#480
ysamcode merged 1 commit into
developfrom
fix/guard-string-component-variable-values

Conversation

@ysamcode

Copy link
Copy Markdown
Collaborator

Summary

A component variable's default_value is untyped in the MCP tool schema, so an agent can store a bare string ("Accessories") where the renderer expects { type: 'dynamic_text', data: { content: 'Accessories' } }. Probing that primitive with the in operator throws TypeError: Cannot use 'in' operator to search for 'type' in Accessories, fetchPageByPath swallows the error and returns null, so every page using the component falls back to the starter template — in the builder preview and on the published site.

Found on a live site whose header component held two such values: all 74 pages served the default "Welcome to Ycode" screen instead of their content.

This hardens the render path so existing bad data recovers without a migration, and closes the write path so new values are stored in the correct shape.

Changes

  • Add normalizeComponentVariableValue in lib/variable-utils.ts, coercing a bare string to dynamic_text and rejecting other primitives
  • Normalize before the in probe in applyComponentOverrides (lib/resolve-components.ts) — the crash site
  • Normalize in extractTiptapFromComponentVariable, which had the same unguarded probe
  • Guard the layer-variable read in resolveLayerAssets (lib/page-fetcher.ts) against a primitive
  • Wrap bare-string default_values at the MCP boundary in normalizeVariables (lib/mcp/tools/components.ts) — Tiptap for rich_text variables, dynamic_text otherwise

set_component_instance already takes a typed text: z.string() and builds the value itself, so default_value was the only unvalidated path.

Test plan

  • Create a component via MCP create_component with default_value: "Some label" on a text variable, add an instance to a page, and confirm the page renders the label instead of falling back to the starter template
  • Repeat with a rich_text variable and confirm the string is stored as Tiptap content
  • Set a variable's default_value to a bare string directly in the database, then load a page using that component — should render the string, not 500
  • Regression: components with well-formed { type, data } defaults, per-instance overrides, and variant switching all render unchanged
  • Regression: image, link, icon, and variant variables (object values without a type key) are unaffected
  • npm run lint and npm run type-check pass

Made with Cursor

A component variable `default_value` is untyped in the MCP tool schema, so an
agent can store a bare string where the renderer expects
`{ type: 'dynamic_text', data: { content } }`. Probing that primitive with the
`in` operator threw, `fetchPageByPath` swallowed the error, and every page
using the component fell back to the starter template — both in the builder
preview and on the published site.

Coerce strings to `dynamic_text` at render time so existing bad data recovers
without a migration, and wrap them at the MCP boundary so new writes are
stored in the correct shape.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ysamcode
ysamcode merged commit a2f7998 into develop Jul 30, 2026
3 checks passed
@ysamcode
ysamcode deleted the fix/guard-string-component-variable-values branch July 30, 2026 09:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant