Skip to content

fix: coerce AutoCompleteInput value to string to stop substring crash - #6959

Draft
superplanehq-integration[bot] wants to merge 2 commits into
mainfrom
fix/typeerror-s-substring-is-not-a-function-1787898769-0d6e71f8
Draft

fix: coerce AutoCompleteInput value to string to stop substring crash #6959
superplanehq-integration[bot] wants to merge 2 commits into
mainfrom
fix/typeerror-s-substring-is-not-a-function-1787898769-0d6e71f8

Conversation

@superplanehq-integration

Copy link
Copy Markdown
Contributor

Work Order.

Summary

AutoCompleteInput crashed with TypeError: S.substring is not a function (Sentry issue 7695184502) when it received a non-string value, for example a number or boolean stored in node configuration. The component stored value in state without checking its type, then called .substring() on it to measure the cursor position. This fix coerces the value to a string at the point where it enters the component state, and also fixes two field renderers that were passing unchecked values into the input. Tests were added to confirm the fix.

UI

  • AutoCompleteInput.tsx

    • Added a small helper, toInputString(value), that returns "" for null/undefined and String(value) for anything else.
    • Used this helper everywhere value first enters the component state: the initial useState, the previousInputValue ref, and the effect that re-syncs state when the value prop changes.
    • Added an extra coercion at the cursor-measurement call site (measureCursorPixelPosition) as a second layer of protection, so the crash cannot happen even if a non-string value reaches that code by another path.
    • This makes inputValue always a string, so all other string-only calls in the component (.trim(), .slice(), .length, etc.) are also protected, not only the one that crashed.
  • StringFieldRenderer.tsx

    • Replaced an unsafe (value as string) ?? "" cast, which does not check the value at runtime, with value == null ? "" : String(value). This matches the pattern already used by TextFieldRenderer.
  • ExpressionFieldRenderer.tsx

    • Replaced the same kind of unsafe cast with a runtime-safe coercion, applied to value or, if that is missing, field.defaultValue.

No changes were made to the API, workers, or database. This is a frontend-only fix.

Tests

Added new test files, colocated with the components:

  • AutoCompleteInput.spec.tsx: renders the component with number, boolean, object, null, and undefined values and confirms it does not crash and shows the coerced text. Also confirms normal string input and typing still work as before.
  • StringFieldRenderer.spec.tsx and ExpressionFieldRenderer.spec.tsx: render each renderer with a non-string value, in both the expression (AutoCompleteInput) and plain (Input) modes, and confirm no crash and correct coerced text.

Test plan

  • New unit tests pass (AutoCompleteInput.spec.tsx, StringFieldRenderer.spec.tsx, ExpressionFieldRenderer.spec.tsx)
  • Existing tests still pass
  • Lint checks pass
  • Build succeeds

Created via SuperPlane.

…769-0d6e71f8

Signed-off-by: SuperPlane Agent <superplaneagent@superplane.com>
AutoCompleteInput kept its editable state (inputValue) as a direct copy
of the value prop, even though callers such as StringFieldRenderer and
ExpressionFieldRenderer sometimes pass non-string config data (numbers,
booleans, objects) despite the prop being typed as value?: string.

When a non-string value reached the component, measureCursorPixelPosition
called inputValue.substring(0, cursorPosition) on every value/cursor
change and crashed with "TypeError: S.substring is not a function"
(Sentry issue 7695184502), regressing PR #6014.

Fix by coercing at the single source of truth: the initial useState,
the previousInputValue ref, and the prop-sync effect all now normalize
via a small toInputString() helper (value == null ? "" : String(value)),
matching the pattern already used by TextFieldRenderer. This guarantees
inputValue is always a string, so every other .substring/.trim/.slice
call site in the component is protected, not just the one that crashed.
Also added a defensive String() coercion at the measure-effect call site
itself as a cheap extra guard.

StringFieldRenderer and ExpressionFieldRenderer previously built their
displayed value with a compile-time-only `as string` cast, which lets
non-string config values pass straight through to AutoCompleteInput.
Both now use the same null-safe String() coercion as TextFieldRenderer.

Added regression tests covering number/boolean/object/null/undefined
values for AutoCompleteInput and both field renderers, plus a happy-path
check that normal string editing still calls onChange as expected.

Signed-off-by: SuperPlane Agent <superplaneagent@superplane.com>
@superplanehq-integration

Copy link
Copy Markdown
Contributor Author

Maintainers: comment /deploy-storybook to get a Storybook link for this PR.

@superplanehq-integration

Copy link
Copy Markdown
Contributor Author

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Working

Development

Successfully merging this pull request may close these issues.

1 participant