Skip to content

fix(site/src/pages/TemplateBuilder): validate customizations form before submit - #28081

Draft
jeremyruppel wants to merge 1 commit into
mainfrom
jeremy/devex-744-template-builder-form-validation
Draft

fix(site/src/pages/TemplateBuilder): validate customizations form before submit#28081
jeremyruppel wants to merge 1 commit into
mainfrom
jeremy/devex-744-template-builder-form-validation

Conversation

@jeremyruppel

@jeremyruppel jeremyruppel commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the unhelpful backend error when creating a template in the Template Builder with no organization selected (DEVEX-744).

The customizations step is now a Formik form with a Yup schema. Validation runs on submit and errors are aggregated in a single Alert at the top of the step, rather than inline under fields, which preserves the horizontal two-column layout. Only valid submits reach the create API, so an empty organization now shows "Select an organization to continue." instead of hitting the backend. A genuinely invalid selection still surfaces the backend's message via the existing error alert.

Changes

  • TemplateCustomizationsStep.tsx: Formik + Yup form; organization is always required (the page is gated on the create-template permission, so there is always at least one permitted org and it is auto-selected when only one exists); aggregated top-of-step error summary.
  • TemplateBuilderPageView.tsx: the wizard's "Create Template" button submits the step form via the form attribute; added onCreate plumbing; relaxed the customizations continue gate to the provisioner check.
  • wizardState.ts: toCreateTemplateRequest(state, customizations) builds the request from the submitted form values; removed the now-vestigial organizationId from the reducer.
  • TemplateBuilderPage.tsx: adapts the create call to the new signature.
  • TemplateCustomizationsStep.stories.tsx: interaction tests for missing organization, missing name, single-org auto-select, and valid submit.

Testing

  • pnpm --dir site exec tsc -p . --noEmit (clean)
  • pnpm --dir site exec biome check src/pages/TemplateBuilder (clean)
  • wizardState.test.ts (27 passed) and TemplateCustomizationsStep.stories.tsx (4 passed)
Screen.Recording.2026-08-12.at.3.52.37.PM.mov
Implementation plan

DEVEX-744: Form validation for the Template Builder customizations step

Problem

In the new Template Builder wizard, creating a template with no organization selected produces an unhelpful backend error (empty organization_id sent to the API). There is no client-side validation on the final "Customizations" step: computeCanContinue only checks name and provisioner availability, so the "Create Template" button is enabled even when the organization is missing.

The customizations form is a horizontal two-column grid. Showing per-field inline errors (Formik helperText) would push fields down and break column alignment. We want validation before submit, with messages aggregated at the top of the step rather than inline under fields.

Decision (confirmed with user)

  • Formik-wrap the customizations step (idiomatic, mirrors the old CreateTemplateForm), rather than ad-hoc reducer validation.
  • Validate organization + all required fields (organization, template ID/name, plus display name/description/icon format limits), reusing the shared Yup validators in utils/formUtils.ts.

Approach

Make TemplateCustomizationsStep a Formik form that is the source of truth for the five customization fields. Validate on submit; render an aggregated error summary Alert at the top of the step. Connect the wizard's existing "Create Template" button (which lives in the parent nav bar) to the child form using the standard form="<id>" type="submit" HTML association, so no imperative handle or extra callback is needed. Only valid submits reach the API.

Steps

  1. TemplateCustomizationsStep.tsx: convert to Formik with a Yup schema (nameValidator, displayNameValidator, description max 128, iconValidator, organization always required); initial values seeded from wizard state; aggregated error Alert at the top; keep provisioner reporting and org auto-select.
  2. TemplateBuilderPageView.tsx: wire the last-step button to submit the form via the form attribute; add an onCreate prop; relax the customizations continue gate to the provisioner check.
  3. wizardState.ts: toCreateTemplateRequest(state, customizations) composes the request from base/modules in state plus submitted values.
  4. TemplateBuilderPage.tsx: adapt handleCreate.

Behavior notes

  • A genuinely invalid organization still surfaces the backend's message via the existing createError alert; only the empty-org case is caught client-side.
  • Unsaved edits to display name/description/icon are discarded when navigating Back off the last step (Formik unmounts and re-seeds from base defaults), matching the wizard's existing reset semantics.

Post-plan refinements (from review)

  • Organization is unconditionally required (removed the conditional orgRequired), since the page is permission-gated and always has at least one permitted org.
  • toCreateTemplateRequest keeps the submitted form values authoritative (no fallback to state, which would resurrect optional fields a user deliberately cleared).
  • Removed the now-dead organizationId field from the reducer state.
  • Story reuses the real query-key builders and the shared Button component.

Testing

  • Storybook interaction tests for the customizations step (missing org, missing name, single-org auto-select, valid submit).
  • Updated wizardState.test.ts for the new toCreateTemplateRequest signature.

Generated by Coder Agents on behalf of @jeremyruppel.

…ore submit

Convert the Template Builder customizations step to a Formik form with a Yup
schema and surface aggregated validation errors in an Alert at the top of the
step. This avoids inline field errors that would disrupt the horizontal
two-column layout, and stops empty organization/name submissions from hitting
the API with an unhelpful backend error.

The wizard's Create Template button submits the step form via the form
attribute. Only valid submits reach the create mutation; a genuinely invalid
organization still surfaces the backend message via the existing error alert.
@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

DEVEX-744

TemplateBuilderWizardState,
} from "./wizardState";

export const TEMPLATE_CUSTOMIZATIONS_FORM_ID = "template-customizations-form";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels slightly hacky to me so suggestions welcome! right now this is used to point the submit button at the form since the button lives outside this view


export const TEMPLATE_CUSTOMIZATIONS_FORM_ID = "template-customizations-form";

const MAX_DESCRIPTION_CHAR_LIMIT = 128;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not positive what the actual length limit is for descriptions (and I don't recall there being one in the template builder package). this is, in all likelihood, validated somewhere but I'm at a loss. totally fine keeping it this way but it smells like duplication

@jeremyruppel
jeremyruppel marked this pull request as ready for review August 12, 2026 20:13
@jeremyruppel

Copy link
Copy Markdown
Contributor Author

some good feedback in slack, so I'm going to take this back into draft for now

@jeremyruppel
jeremyruppel marked this pull request as draft August 13, 2026 13:51
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