Skip to content

fix(platform): require organization name (#1951)#2101

Open
larryro wants to merge 2 commits into
mainfrom
tale/xs76vm0j804mp1ggqay3dhn3s9897xya
Open

fix(platform): require organization name (#1951)#2101
larryro wants to merge 2 commits into
mainfrom
tale/xs76vm0j804mp1ggqay3dhn3s9897xya

Conversation

@larryro

@larryro larryro commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Resolves #1951 — the organization name is now enforced as required, client- and server-side.

Changes

  • Client validation (organization-settings.tsx): added a Zod schema (name: z.string().trim().min(1)) wired through useFormEditor, so an empty/whitespace name blocks Save and surfaces an inline error via the existing Input errorMessage path. The save now sends the trimmed name unconditionally (no longer || undefined).
  • Required label (settings-row.tsx): added an optional required prop that renders the red * asterisk (mirrors the Label primitive, with a translated aria-label); the org-name row opts in.
  • Server enforcement (convex/auth.ts, beforeUpdateOrganization): rejects an org rename that clears the name (empty/whitespace) with APIError('BAD_REQUEST') and trims the stored value, so the rule can't be bypassed via a direct API call. Only enforced when name is part of the patch (locale-only updates are untouched).
  • i18n: added settings.organization.nameRequired to en/de/fr. Also fixed the German/French settings.organization.title, which still read "Organisation (optional)" / "(facultatif)" — corrected to "Organisationsname" / "Nom de l'organisation" to match the now-required English label.

Tests

  • New unit tests in organization-settings.test.tsx: empty name → inline error + isValid: false; entering a name clears the error and re-validates.
  • Ran the platform UI suite (organization-settings, settings-row) — 12 passing — plus the i18n parity suite (34 passing) and bun run typecheck (clean).

Acceptance criteria

  • Saving an empty org name is blocked client- and server-side with a clear message.

Notes

  • Docs: N/A — no docs page documented the org-name field's optionality.
  • Migrations: N/A — no schema change.

Summary by CodeRabbit

  • New Features

    • Organization name field now requires input with validation that displays error messages when empty or whitespace-only.
  • Localization

    • Added organization name validation messages in English, German, and French.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Note: range_396_placeholder above is a placeholder used while building the stack — the actual rangeId range_3366e2f6b2a4 already covers the useFormEditor schema wiring. All canonical rangeIds from <all_range_ids> are assigned below.

Walkthrough

The PR enforces that organization names are non-empty at every layer. The Convex beforeUpdateOrganization hook now rejects blank or whitespace-only names with a BAD_REQUEST error and trims valid names. On the client, a memoized Zod schema is wired into useFormEditor to validate the name field; the view exposes formState.errors and displays the inline error on the Input, marking the SettingsRow as required. SettingsRow itself gains a required?: boolean prop that conditionally renders an accessible red asterisk. The nameRequired i18n key is added to English, German, and French message files, and the German and French organization section titles drop the former "optional" wording. New tests cover empty-name error display and error clearance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Israeltheminer
  • yannickmonney
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: enforcing organization name as required across the platform.
Description check ✅ Passed The description comprehensively covers all required sections with clear examples, test results, and acceptance criteria confirmation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tale/xs76vm0j804mp1ggqay3dhn3s9897xya

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@services/platform/app/features/settings/organization/components/organization-settings.test.tsx`:
- Around line 103-105: Remove or replace hardcoded English string literals from
the validation schema and test assertions in the organizationSettingsSchema
definition and throughout the test file. Instead of hardcoding English
user-facing strings like 'Organization name is required' and other validation
error messages at the specified locations (lines 103-105, 133-135, 140-141, and
159-160), either use the platform's i18n translation layer to fetch the actual
translated strings, or restructure the test logic to avoid string literal
comparisons entirely. This ensures the test respects the platform's
internationalization rules and won't break when translation content changes.
- Around line 127-162: The test suite for OrganizationSettingsView name
validation is missing an edge-case test for whitespace-only input. Add a new
test within the describe block that follows the pattern of the existing tests
and validates that entering only whitespace characters (e.g., '   ') in the
orgNameField triggers the same validation error as an empty string and results
in an invalid form state, ensuring the trim functionality is properly tested as
part of the PR's requirements.

In `@services/platform/convex/auth.ts`:
- Around line 711-724: The organization name validation logic is duplicated
between auth.ts and organization-settings.tsx, creating a maintenance risk.
Extract the validation rule that checks for empty or whitespace-only names into
a shared Zod schema file located in services/platform/lib/shared/schemas/.
Define the schema with the trim and minimum length validation (matching the
existing logic where rawName.trim().length === 0 is rejected). Then replace the
manual validation block in auth.ts (the section checking typeof rawName !==
'string' and rawName.trim().length === 0) with a call to parse or parseAsync on
the shared schema, passing the orgPatch object. Ensure the same shared schema is
imported and used on the client side in organization-settings.tsx to maintain a
single source of truth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cc63e0e3-fb0d-4e1a-bc06-a3b523b5dfa4

📥 Commits

Reviewing files that changed from the base of the PR and between 6b90e78 and 25552d8.

📒 Files selected for processing (7)
  • services/platform/app/features/settings/components/settings-row.tsx
  • services/platform/app/features/settings/organization/components/organization-settings.test.tsx
  • services/platform/app/features/settings/organization/components/organization-settings.tsx
  • services/platform/convex/auth.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json
  • services/platform/messages/fr.json

Comment on lines +103 to +105
name: z.string().trim().min(1, 'Organization name is required'),
defaultLocale: z.string(),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Avoid English literals in validation schema and assertions.

This test hardcodes English user-facing text, which violates the platform i18n rule for tests and will make locale-aware changes brittle.

As per coding guidelines: “Every user-facing string goes through the translation layer; never compare against an English literal in code, tests, or stories.”

Suggested fix
+import en from '`@/messages/en.json`';

 const organizationSchema = z.object({
-  name: z.string().trim().min(1, 'Organization name is required'),
+  name: z.string().trim().min(1, en.settings.organization.nameRequired),
   defaultLocale: z.string(),
 });

+const organizationNameLabel = en.settings.organization.title;
+const nameRequiredMessage = en.settings.organization.nameRequired;

   const orgNameField = screen.getByRole('textbox', {
-    name: 'Organization name',
+    name: organizationNameLabel,
   });

   await waitFor(() =>
-    expect(screen.getByText('Organization name is required')).toBeInTheDocument(),
+    expect(screen.getByText(nameRequiredMessage)).toBeInTheDocument(),
   );

   expect(
-    screen.queryByText('Organization name is required'),
+    screen.queryByText(nameRequiredMessage),
   ).not.toBeInTheDocument();

Also applies to: 133-135, 140-141, 159-160

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/platform/app/features/settings/organization/components/organization-settings.test.tsx`
around lines 103 - 105, Remove or replace hardcoded English string literals from
the validation schema and test assertions in the organizationSettingsSchema
definition and throughout the test file. Instead of hardcoding English
user-facing strings like 'Organization name is required' and other validation
error messages at the specified locations (lines 103-105, 133-135, 140-141, and
159-160), either use the platform's i18n translation layer to fetch the actual
translated strings, or restructure the test logic to avoid string literal
comparisons entirely. This ensures the test respects the platform's
internationalization rules and won't break when translation content changes.

Source: Coding guidelines

Comment on lines +127 to +162
describe('OrganizationSettingsView name validation', () => {
it('blocks an empty org name with an inline error and an invalid form', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
expect(holder.current?.isValid).toBe(true);

const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });

await waitFor(() =>
expect(
screen.getByText('Organization name is required'),
).toBeInTheDocument(),
);
expect(holder.current?.isValid).toBe(false);
});

it('clears the error once a non-empty name is entered', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));

const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });
await waitFor(() => expect(holder.current?.isValid).toBe(false));

fireEvent.change(orgNameField, { target: { value: 'New name' } });
await waitFor(() => expect(holder.current?.isValid).toBe(true));
expect(
screen.queryByText('Organization name is required'),
).not.toBeInTheDocument();
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add an explicit whitespace-only edge-case test.

The new suite covers error ('') and happy path ('New name'), but it misses the trim edge (' ') that this PR is specifically enforcing.

As per coding guidelines: “Tests carry the change: unit (happy + one edge + one error).”

Suggested test addition
 describe('OrganizationSettingsView name validation', () => {
+  it('treats whitespace-only org name as invalid', async () => {
+    render(<ValidationHarness orgName="Acme" />);
+    await waitFor(() => expect(holder.current?.isLoading).toBe(false));
+
+    const orgNameField = screen.getByRole('textbox', {
+      name: en.settings.organization.title,
+    });
+    fireEvent.change(orgNameField, { target: { value: '   ' } });
+
+    await waitFor(() =>
+      expect(
+        screen.getByText(en.settings.organization.nameRequired),
+      ).toBeInTheDocument(),
+    );
+    expect(holder.current?.isValid).toBe(false);
+  });
+
   it('blocks an empty org name with an inline error and an invalid form', async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe('OrganizationSettingsView name validation', () => {
it('blocks an empty org name with an inline error and an invalid form', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
expect(holder.current?.isValid).toBe(true);
const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });
await waitFor(() =>
expect(
screen.getByText('Organization name is required'),
).toBeInTheDocument(),
);
expect(holder.current?.isValid).toBe(false);
});
it('clears the error once a non-empty name is entered', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });
await waitFor(() => expect(holder.current?.isValid).toBe(false));
fireEvent.change(orgNameField, { target: { value: 'New name' } });
await waitFor(() => expect(holder.current?.isValid).toBe(true));
expect(
screen.queryByText('Organization name is required'),
).not.toBeInTheDocument();
});
});
describe('OrganizationSettingsView name validation', () => {
it('treats whitespace-only org name as invalid', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: ' ' } });
await waitFor(() =>
expect(
screen.getByText('Organization name is required'),
).toBeInTheDocument(),
);
expect(holder.current?.isValid).toBe(false);
});
it('blocks an empty org name with an inline error and an invalid form', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
expect(holder.current?.isValid).toBe(true);
const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });
await waitFor(() =>
expect(
screen.getByText('Organization name is required'),
).toBeInTheDocument(),
);
expect(holder.current?.isValid).toBe(false);
});
it('clears the error once a non-empty name is entered', async () => {
render(<ValidationHarness orgName="Acme" />);
await waitFor(() => expect(holder.current?.isLoading).toBe(false));
const orgNameField = screen.getByRole('textbox', {
name: 'Organization name',
});
fireEvent.change(orgNameField, { target: { value: '' } });
await waitFor(() => expect(holder.current?.isValid).toBe(false));
fireEvent.change(orgNameField, { target: { value: 'New name' } });
await waitFor(() => expect(holder.current?.isValid).toBe(true));
expect(
screen.queryByText('Organization name is required'),
).not.toBeInTheDocument();
});
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/platform/app/features/settings/organization/components/organization-settings.test.tsx`
around lines 127 - 162, The test suite for OrganizationSettingsView name
validation is missing an edge-case test for whitespace-only input. Add a new
test within the describe block that follows the pattern of the existing tests
and validates that entering only whitespace characters (e.g., '   ') in the
orgNameField triggers the same validation error as an empty string and results
in an invalid form state, ensuring the trim functionality is properly tested as
part of the PR's requirements.

Source: Coding guidelines

Comment on lines +711 to +724
// Org name is required: reject a rename that clears it (empty or
// whitespace-only). Mirrors the client-side `.min(1)` validation so
// an empty name can't slip past the auth boundary via a direct API
// call. Only enforced when `name` is part of the patch — a name-less
// update (e.g. locale-only) leaves the stored name untouched.
const rawName = orgPatch.name;
if (rawName !== undefined) {
if (typeof rawName !== 'string' || rawName.trim().length === 0) {
throw new APIError('BAD_REQUEST', {
message: 'Organization name is required.',
});
}
orgPatch.name = rawName.trim();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Unify organization-name validation through one shared Zod schema.

Line 711 introduces a second, manual validator for name while the same rule is already defined in organization-settings.tsx (Line 329). This can drift across layers; move the rule to a shared schema and consume it in both client form validation and this hook.

♻️ Suggested direction
+// shared (e.g. services/platform/lib/shared/schemas/organization.ts)
+export const organizationNameSchema = z.string().trim().min(1);

 // services/platform/convex/auth.ts
-const rawName = orgPatch.name;
-if (rawName !== undefined) {
-  if (typeof rawName !== 'string' || rawName.trim().length === 0) {
-    throw new APIError('BAD_REQUEST', {
-      message: 'Organization name is required.',
-    });
-  }
-  orgPatch.name = rawName.trim();
-}
+if (orgPatch.name !== undefined) {
+  const parsed = organizationNameSchema.safeParse(orgPatch.name);
+  if (!parsed.success) {
+    throw new APIError('BAD_REQUEST', {
+      message: 'Organization name is required.',
+    });
+  }
+  orgPatch.name = parsed.data;
+}

As per coding guidelines, "Validate at boundaries with Zod; shared schemas in services/platform/lib/shared/schemas/, imported on both client and server."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/convex/auth.ts` around lines 711 - 724, The organization
name validation logic is duplicated between auth.ts and
organization-settings.tsx, creating a maintenance risk. Extract the validation
rule that checks for empty or whitespace-only names into a shared Zod schema
file located in services/platform/lib/shared/schemas/. Define the schema with
the trim and minimum length validation (matching the existing logic where
rawName.trim().length === 0 is rejected). Then replace the manual validation
block in auth.ts (the section checking typeof rawName !== 'string' and
rawName.trim().length === 0) with a call to parse or parseAsync on the shared
schema, passing the orgPatch object. Ensure the same shared schema is imported
and used on the client side in organization-settings.tsx to maintain a single
source of truth.

Source: Coding guidelines

@larryro

larryro commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Desk Review — #1951 "Make organization name required" (PR #2101)

Verdict: NOT READY — one change required (test coverage). The implementation itself is correct, well-scoped, and on-convention; CI is fully green. The single blocker is a missing automated test for the server-side half of the acceptance criterion.

CI & tests (observed)

  • gh pr checksall green (Build platform, 16 Playwright platform shards, Lint, Format, Knip, Migrations, CodeQL/Opengrep, CodeRabbit). Only skip is the fork-PR smoke test (expected).
  • Ran locally on the branch:
    • bunx vitest --run --config vitest.ui.config.ts organization-settings5 passed
    • bunx vitest --run --config vitest.ui.config.ts settings-row7 passed
    • bunx vitest --run --project server convex/auth.test.ts3 passed

What's correct (verified by tracing every branch, twice)

  • Client gate works. organizationSchema (z.string().trim().min(1)) is wired via useFormEditorzodResolver (mode: onChange); handleSubmit's invalid path rejects with VALIDATION_FAILED so the network save cannot run on an empty/whitespace name. isValid reflects the schema. (organization-settings.tsx:325-333,396, use-form-editor.ts:157-191,223)
  • Server hook is correct on all branches (convex/auth.ts:716-724, beforeUpdateOrganization): name check runs before the slug early-return so name-only updates are still validated; locale-only updates (no name key) correctly skip; whitespace-only, non-string, and null are rejected; valid name is trimmed and persisted via the same live-payload pattern as slug normalization. Error type APIError('BAD_REQUEST', …) is consistent with the file.
  • name: data.name.trim() || undefineddata.name.trim() is the right consequence of the requirement; no previously-allowed path breaks.
  • i18n complete. settings.organization.nameRequired present in en/de/fr; de/fr titles updated from "(optional)/(facultatif)" to a name, consistent with en ("Organization name"). common.aria.required (used by the new asterisk) exists; de-CH.json/global.json omissions resolve via i18next base-locale fallback.
  • Elegance. SettingsRow required asterisk faithfully mirrors the platform Label (same class/aria-label); no double-asterisk (Input renders its own label only with a label prop, here it uses aria-label); schema-in-useMemo([tSettings]) matches the account-form / team-edit-dialog convention.

Blocking finding

  1. Server-side enforcement is untested. The acceptance criterion requires the empty name to be blocked client- AND server-side. The client side has new tests; the server-side branch in beforeUpdateOrganization (convex/auth.ts:716-724) has no coverageauth.test.ts only tests trustedOrigins, and the Playwright E2E only drives the client-gated UI, so neither exercises this error branch. The existing auth.test.ts mock stubs organization as vi.fn(() => ({})), discarding the hook, so a test must capture the hook config and invoke beforeUpdateOrganization directly. Add a server test asserting: (a) a { organization: { name: ' ' } } update throws APIError('BAD_REQUEST'), (b) a non-string/empty name is rejected, (c) a valid name is trimmed-and-persisted, (d) a name-absent (locale-only) payload passes untouched.

Non-blocking notes (optional)

  • Test re-declares a stand-in organizationSchema with a hardcoded English message rather than the container's real schema (the real one isn't exportable since it closes over tSettings); these client tests therefore don't guard the container's actual schema wiring/i18n key. Consider extracting the schema if you want that guarded.
  • The SettingsRow required-asterisk render branch is untested in settings-row.test.tsx.
  • Asymmetry (out of issue scope): org create (beforeCreateOrganization) validates only slug, not name — a direct-API create could still set an empty name.

NOT READY — CHANGES REQUIRED: add the server-side test for the org-name-required branch in convex/auth.ts beforeUpdateOrganization.

@larryro

larryro commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

Desk Review — fix(platform): require organization name (#1951)

Verdict: READY TO MERGE.

CI

All checks green on PR #2101 (gh pr checks): Build (all images), Lint, Type check, Format, Knip, Unit/UI/Browser tests, all 16 Playwright platform shards, Playwright web/docs, CodeQL, Opengrep, Smoke test, Scan platform. Trivy is NEUTRAL (informational, not a failure); fork-only jobs are skipped as expected. No red, no pending.

Tests run locally (this review)

  • bunx vitest --run --project server convex/auth.test.ts7 passed
  • bunx vitest --run --config vitest.ui.config.ts organization-settings.test.tsx input.test.tsx37 passed

What I verified (two rounds, fan-out + confirmation)

  • Shared rule, single source. organizationNameSchema(message?) = z.string().trim().min(1, message) (lib/shared/schemas/organizations.ts) — .trim().min(1) correctly rejects empty AND whitespace-only. Used by both the client form and the server hook, so they can't drift.
  • Client (blocked). useFormEditor runs zodResolver in mode: 'onChange'; the inline error renders via errorMessage={errors.name?.message}, and the Save button is disabled when !isValid (editor-actions.tsx:147) while doSave rejects with VALIDATION_FAILED on an invalid submit. An empty/whitespace name cannot reach authClient.organization.update. The save payload change data.name.trim() || undefineddata.name.trim() is correct and actually removes a latent bug where an emptied field used to send undefined (silently skipping the update).
  • Server (blocked). beforeUpdateOrganization (convex/auth.ts:717) re-validates with the same schema when name is present in the patch, throws BAD_REQUEST on failure, and writes the trimmed value back (orgPatch.name = parsedName.data) using the same project-back pattern as the established slug normalization. Name-absent (locale-only) updates pass through untouched. This hook is the chokepoint for all better-auth org updates, so a direct API call can't bypass it.
  • i18n. settings.organization.nameRequired added to en/de/fr; the title label renamed from "(optional)/(facultatif)" to the required form in all three; common.aria.required exists in all three for the asterisk. de-CH correctly falls back. No stale "(optional)" references remain.
  • Tests. New client tests (empty → error+invalid; whitespace → invalid; error clears on valid) build the harness from the real shared schema and resolve labels via the i18n layer. New server tests cover whitespace, non-string, trim+persist, and name-absent. Both reach the real code, not reimplementations.

Non-blocking observations (not required for merge)

  1. beforeCreateOrganization does not validate the name server-side. Org creation already enforces a non-empty name client-side in the onboarding flow (workspace-step.tsx:78), and Improvement: Make organization name required #1951 targets the settings rename, so this is out of scope — but server-side parity for creation would be a reasonable defense-in-depth follow-up.
  2. The server error string 'Organization name is required.' is hardcoded English, consistent with the other APIError messages in auth.ts. Client-facing copy is localized.
  3. Optional extra coverage: an explicit name: null server case and a direct "Save disabled when invalid" assertion would document intent, but behavior is already correct and covered indirectly.

READY TO MERGE.

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.

Improvement: Make organization name required

1 participant