diff --git a/.agents/skills/generate-frontend-forms/SKILL.md b/.agents/skills/generate-frontend-forms/SKILL.md
index 5e8873408eb3..221f7cb90cc9 100644
--- a/.agents/skills/generate-frontend-forms/SKILL.md
+++ b/.agents/skills/generate-frontend-forms/SKILL.md
@@ -65,18 +65,16 @@ function MyForm() {
});
return (
-
-
-
- {field => (
-
-
-
- )}
-
-
- Submit
-
+
+
+ {field => (
+
+
+
+ )}
+
+
+ Submit
);
}
@@ -86,16 +84,15 @@ function MyForm() {
### Returned Properties
-| Property | Description |
-| ---------------- | ---------------------------------------------- |
-| `AppForm` | Root wrapper component (provides form context) |
-| `FormWrapper` | Form element wrapper (handles submit) |
-| `AppField` | Field renderer component |
-| `FieldGroup` | Section grouping with title |
-| `SubmitButton` | Pre-wired submit button |
-| `Subscribe` | Subscribe to form state changes |
-| `reset()` | Reset form to default values |
-| `handleSubmit()` | Manually trigger submission |
+| Property | Description |
+| ---------------- | ------------------------------------------------------------------------------------------------------------- |
+| `AppForm` | Root wrapper component (provides form context and renders `` and ``
+- [ ] Wrap with ``
- [ ] Use `` for each field
- [ ] Choose appropriate layout (Stack or Row)
- [ ] Handle server errors with `setFieldErrors`
diff --git a/.agents/skills/migrate-frontend-forms/SKILL.md b/.agents/skills/migrate-frontend-forms/SKILL.md
index ec2f24e5f066..1a5a0d85bb9f 100644
--- a/.agents/skills/migrate-frontend-forms/SKILL.md
+++ b/.agents/skills/migrate-frontend-forms/SKILL.md
@@ -452,7 +452,7 @@ function SlugForm({project}: {project: Project}) {
});
return (
-
+
{field => (
diff --git a/static/app/components/core/form/field/autoSaveField.tsx b/static/app/components/core/form/field/autoSaveField.tsx
index a7663daab885..eb17d4634fa9 100644
--- a/static/app/components/core/form/field/autoSaveField.tsx
+++ b/static/app/components/core/form/field/autoSaveField.tsx
@@ -231,11 +231,9 @@ export function AutoSaveField<
});
return (
-
+
-
- {field => children(field as never)}
-
+ {field => children(field as never)}
);
diff --git a/static/app/components/core/form/field/baseField.spec.tsx b/static/app/components/core/form/field/baseField.spec.tsx
index dde85abf5bd0..1ba6a7632d87 100644
--- a/static/app/components/core/form/field/baseField.spec.tsx
+++ b/static/app/components/core/form/field/baseField.spec.tsx
@@ -22,7 +22,7 @@ function TestForm({label, hintText, required, defaultValue, validator}: TestForm
});
return (
-
+
{field => (
@@ -49,7 +49,7 @@ function CompactTestForm({label, hintText, layout = 'Row'}: CompactTestFormProps
});
return (
-
+
{field => {
const LayoutComponent = field.Layout[layout];
diff --git a/static/app/components/core/form/field/passwordField.spec.tsx b/static/app/components/core/form/field/passwordField.spec.tsx
index 571de11aaf11..01c2ff10be23 100644
--- a/static/app/components/core/form/field/passwordField.spec.tsx
+++ b/static/app/components/core/form/field/passwordField.spec.tsx
@@ -15,7 +15,7 @@ function TestForm({defaultValue = '', disabled, label = 'Password'}: TestFormPro
});
return (
-
+
{field => (
diff --git a/static/app/components/core/form/field/radioField.spec.tsx b/static/app/components/core/form/field/radioField.spec.tsx
index e0018995bc2a..0b49c0bfb5b5 100644
--- a/static/app/components/core/form/field/radioField.spec.tsx
+++ b/static/app/components/core/form/field/radioField.spec.tsx
@@ -27,7 +27,7 @@ function TestForm({
});
return (
-
+
{field => (
+
{field => (
diff --git a/static/app/components/core/form/field/selectField.spec.tsx b/static/app/components/core/form/field/selectField.spec.tsx
index 48d91b8377b7..bbf03dceb099 100644
--- a/static/app/components/core/form/field/selectField.spec.tsx
+++ b/static/app/components/core/form/field/selectField.spec.tsx
@@ -39,7 +39,7 @@ function TestForm({
});
return (
-
+
{field => (
@@ -102,7 +102,7 @@ describe('SelectField', () => {
});
return (
-
+
{field => (
{
});
return (
-
+
{field => (
{
});
return (
-
+
{field => (
// @ts-expect-error value should be string[] when multiple is true
@@ -189,7 +189,7 @@ describe('SelectField', () => {
});
return (
-
+
{field => (
// @ts-expect-error value should be string when multiple is false
@@ -214,7 +214,7 @@ describe('SelectField', () => {
});
return (
-
+
{field => (
{
});
return (
-
+
{field => (
+
{field => (
diff --git a/static/app/components/core/form/field/switchField.spec.tsx b/static/app/components/core/form/field/switchField.spec.tsx
index 08433a865f63..140c41505dac 100644
--- a/static/app/components/core/form/field/switchField.spec.tsx
+++ b/static/app/components/core/form/field/switchField.spec.tsx
@@ -36,7 +36,7 @@ function TestForm({
});
return (
-
+
{field => (
diff --git a/static/app/components/core/form/field/textAreaField.spec.tsx b/static/app/components/core/form/field/textAreaField.spec.tsx
index 9ec1fa33d52b..d4e1ed0d1fb4 100644
--- a/static/app/components/core/form/field/textAreaField.spec.tsx
+++ b/static/app/components/core/form/field/textAreaField.spec.tsx
@@ -31,7 +31,7 @@ function TestForm({
});
return (
-
+
{field => (
diff --git a/static/app/components/core/form/form.stories.tsx b/static/app/components/core/form/form.stories.tsx
index 4bfcdf5a8659..ead55099ad12 100644
--- a/static/app/components/core/form/form.stories.tsx
+++ b/static/app/components/core/form/form.stories.tsx
@@ -314,165 +314,163 @@ function BasicForm() {
}
return (
-
-
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
+
+
+ {field => (
+
+
-
-
- Low
- Medium
-
- High
-
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
- state.values.age === 42}>
- {showSecret =>
- showSecret ? (
-
- {field => (
-
-
-
- )}
-
- ) : null
- }
-
-
-
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
+ />
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ Low
+ Medium
+
+ High
+
+
- )}
-
-
-
-
-
- Submit
-
-
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+ state.values.age === 42}>
+ {showSecret =>
+ showSecret ? (
+
+ {field => (
+
+
+
+ )}
+
+ ) : null
+ }
+
+
+
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+
+
+
+ Submit
+
);
}
@@ -489,56 +487,54 @@ function CompactExample() {
});
return (
-
-
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
-
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
-
+
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
);
}
diff --git a/static/app/components/core/form/scrapsForm.tsx b/static/app/components/core/form/scrapsForm.tsx
index ed8c37af585c..bda2b4aec518 100644
--- a/static/app/components/core/form/scrapsForm.tsx
+++ b/static/app/components/core/form/scrapsForm.tsx
@@ -3,6 +3,7 @@ import {
createFormHook,
formOptions,
revalidateLogic,
+ type AnyFormApi,
type DeepKeys,
} from '@tanstack/react-form';
@@ -57,7 +58,7 @@ const {useAppForm} = createFormHook({
formComponents: {
FieldGroup,
SubmitButton,
- FormWrapper,
+ AppForm,
},
fieldContext,
formContext,
@@ -80,6 +81,14 @@ function SubmitButton(props: ButtonProps) {
);
}
+function AppForm({children, form}: {children: React.ReactNode; form: AnyFormApi}) {
+ return (
+
+ {children}
+
+ );
+}
+
function FormWrapper({children}: {children: React.ReactNode}) {
const form = useFormContext();
diff --git a/static/app/components/customCommitsResolutionModal.tsx b/static/app/components/customCommitsResolutionModal.tsx
index a17d7f9c1400..e9f26dabc694 100644
--- a/static/app/components/customCommitsResolutionModal.tsx
+++ b/static/app/components/customCommitsResolutionModal.tsx
@@ -56,57 +56,55 @@ function CustomCommitsResolutionModal({
});
return (
-
-
-
-
-
- {field => (
-
- {
- return queryOptions({
- ...apiOptions.as()(
- '/projects/$organizationIdOrSlug/$projectIdOrSlug/commits/',
- {
- path: {
- organizationIdOrSlug: orgSlug,
- projectIdOrSlug: projectSlug,
- },
- query: {query: debouncedInput},
- staleTime: 30_000,
- }
- ),
- select: ({json: commits}) =>
- commits.map(c => ({
- value: c,
- textValue: c.id,
- label: ,
- details: (
-
- {t('Created')}
-
- ),
- })),
- });
- }}
- placeholder={t('e.g. d86b832')}
- />
-
- )}
-
-
-
-
+
+
+
+
+ {field => (
+
+ {
+ return queryOptions({
+ ...apiOptions.as()(
+ '/projects/$organizationIdOrSlug/$projectIdOrSlug/commits/',
+ {
+ path: {
+ organizationIdOrSlug: orgSlug,
+ projectIdOrSlug: projectSlug,
+ },
+ query: {query: debouncedInput},
+ staleTime: 30_000,
+ }
+ ),
+ select: ({json: commits}) =>
+ commits.map(c => ({
+ value: c,
+ textValue: c.id,
+ label: ,
+ details: (
+
+ {t('Created')}
+
+ ),
+ })),
+ });
+ }}
+ placeholder={t('e.g. d86b832')}
+ />
+
+ )}
+
+
+
);
}
diff --git a/static/app/views/settings/account/accountEmails.tsx b/static/app/views/settings/account/accountEmails.tsx
index f920d7ddd2e1..9a89fd8b39aa 100644
--- a/static/app/views/settings/account/accountEmails.tsx
+++ b/static/app/views/settings/account/accountEmails.tsx
@@ -84,29 +84,27 @@ function AccountEmails() {
-
-
-
-
- {field => (
-
-
-
- )}
-
-
-
- {t('Add email')}
-
-
+
+
+
+ {field => (
+
+
+
+ )}
+
+
+
+ {t('Add email')}
+
diff --git a/static/app/views/settings/account/passwordForm.tsx b/static/app/views/settings/account/passwordForm.tsx
index 19c3880390b2..2cf72b3d1c23 100644
--- a/static/app/views/settings/account/passwordForm.tsx
+++ b/static/app/views/settings/account/passwordForm.tsx
@@ -62,57 +62,55 @@ export function PasswordForm() {
return (
-
-
-
-
- {t('Changing your password will invalidate all logged in sessions.')}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {field => (
-
-
-
- )}
-
-
- {t('Change password')}
-
-
-
+
+
+
+ {t('Changing your password will invalidate all logged in sessions.')}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
+
+ {t('Change password')}
+
+
);
diff --git a/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx b/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx
index 1303e97f1c51..a386365e8c23 100644
--- a/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx
+++ b/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx
@@ -140,39 +140,37 @@ function AuthTokenDetailsForm({token}: {token: OrgAuthToken}) {
});
return (
-
-
-
- {field => (
-
-
-
- )}
-
-
-
- {tokenPreview(token.tokenLastCharacters || '****')}
-
-
-
- {token.scopes.slice().sort().join(', ')}
-
-
-
-
- {t('Save Changes')}
-
-
+
+
+ {field => (
+
+
+
+ )}
+
+
+
+ {tokenPreview(token.tokenLastCharacters || '****')}
+
+
+
+ {token.scopes.slice().sort().join(', ')}
+
+
+
+
+ {t('Save Changes')}
+
);
}
diff --git a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx
index ca9f801f9075..419fc51eebe6 100644
--- a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx
+++ b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx
@@ -97,35 +97,33 @@ function AuthTokenCreateForm({
});
return (
-
-
-
- {field => (
-
-
-
- )}
-
-
-
-
-
org:ci
-
{t('Source Map Upload, Release Creation')}
-
-
-
-
-
- {t('Create Token')}
-
-
+
+
+ {field => (
+
+
+
+ )}
+
+
+
+
+
org:ci
+
{t('Source Map Upload, Release Creation')}
+
+
+
+
+
+ {t('Create Token')}
+
);
}
diff --git a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx
index c22d800b14ce..fe6b8fb45cd9 100644
--- a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx
+++ b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx
@@ -454,55 +454,50 @@ function OrganizationSettingsForm({initialData, onSave}: Props) {
{/* Slug — explicit save with warning */}
-
-
-
- {field => (
-
- field.handleChange(slugify(value))}
- disabled={!hasWriteAccess}
- />
-
- )}
-
- state.values.slug !== initialData.slug}
- >
- {isDirty =>
- isDirty && (
-
-
- {tct(
- 'Changing your organization slug will break organization tokens, may impact integrations, and break links to your organization. You will be redirected to the new slug after saving. [link:Learn more]',
- {
- link: (
-
- ),
- }
- )}
-
-
-
-
- {t('Save')}
-
-
-
- )
- }
-
-
+
+
+ {field => (
+
+ field.handleChange(slugify(value))}
+ disabled={!hasWriteAccess}
+ />
+
+ )}
+
+ state.values.slug !== initialData.slug}
+ >
+ {isDirty =>
+ isDirty && (
+
+
+ {tct(
+ 'Changing your organization slug will break organization tokens, may impact integrations, and break links to your organization. You will be redirected to the new slug after saving. [link:Learn more]',
+ {
+ link: (
+
+ ),
+ }
+ )}
+
+
+
+
+ {t('Save')}
+
+
+
+ )
+ }
+
{/* Display Name */}
diff --git a/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx b/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx
index c6d07a51ba86..75540b2e82fb 100644
--- a/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx
+++ b/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx
@@ -425,80 +425,78 @@ function ScrubbingConfigurationFieldGroup({hasOrgWrite}: {hasOrgWrite: boolean})
return (
-
-
-
-
- {field => (
-
-
-
- )}
-
+
+
+
+ {field => (
+
+
+
+ )}
+
-
- {field => (
-
-
-
- )}
-
- {hasOrgWrite ? (
-
-
- state.values.sensitiveFields !== initialSensitiveFields ||
- state.values.safeFields !== initialSafeFields
- }
- >
- {hasChanged => (
-
-
- {t(
- 'Changes to your scrubbing configuration will apply to all new events.'
- )}
-
-
- )}
-
-
-
-
- {t('Save')}
-
-
+
+ {field => (
+
+
+
+ )}
+
+ {hasOrgWrite ? (
+
+
+ state.values.sensitiveFields !== initialSensitiveFields ||
+ state.values.safeFields !== initialSafeFields
+ }
+ >
+ {hasChanged => (
+
+
+ {t(
+ 'Changes to your scrubbing configuration will apply to all new events.'
+ )}
+
+
+ )}
+
+
+
+
+ {t('Save')}
+
- ) : null}
-
-
+
+ ) : null}
+
);
diff --git a/static/app/views/settings/organizationTeams/teamSettings/index.tsx b/static/app/views/settings/organizationTeams/teamSettings/index.tsx
index b89151f4d1ab..bbc22a780caa 100644
--- a/static/app/views/settings/organizationTeams/teamSettings/index.tsx
+++ b/static/app/views/settings/organizationTeams/teamSettings/index.tsx
@@ -101,65 +101,63 @@ export default function TeamSettings() {
)}
-
-
-
-
- {field => (
-
- field.handleChange(slugify(value))}
- placeholder="e.g. operations, web-frontend, mobile-ios"
- disabled={isDisabled}
- />
-
- )}
-
-
- {field => (
-
-
-
- )}
-
+
+
+
+ {field => (
+
+ field.handleChange(slugify(value))}
+ placeholder="e.g. operations, web-frontend, mobile-ios"
+ disabled={isDisabled}
+ />
+
+ )}
+
+
+ {field => (
+
+
+
+ )}
+
- {isDisabled ? null : (
-
- state.values.slug !== team.slug}>
- {hasChanged => (
-
-
- {t('You will be redirected to the new team slug after saving.')}
-
-
- )}
-
-
-
- {t('Save')}
-
+ {isDisabled ? null : (
+
+ state.values.slug !== team.slug}>
+ {hasChanged => (
+
+
+ {t('You will be redirected to the new team slug after saving.')}
+
+
+ )}
+
+
+
+ {t('Save')}
- )}
-
-
+
+ )}
+