Skip to content

feat(clusters): org-owned custom regions — picker group and units on the cluster form - #1780

Draft
DavidCockerill wants to merge 16 commits into
stagefrom
david/1778-custom-regions
Draft

DavidCockerill wants to merge 16 commits into
stagefrom
david/1778-custom-regions

Conversation

@DavidCockerill

@DavidCockerill DavidCockerill commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Studio side of org-owned custom regions: a cluster form that keys each region plan by id ({ regionId, quantity? }) so a custom region — which has neither a catalog name nor a latency tier — can be picked, sized in units and submitted alongside catalog tiers. Staff with the cluster permission see the organization's custom regions in the picker and can define one inline (the organization admin tab that lists and edits them is a separate PR); a member keeps a custom region staff already placed but cannot add or swap one. Closes #1778 (PR 2 of the plan in central-manager#889; pairs with central-manager#892 — Org-owned custom regions with deploy-time quantity: a central-manager without the resource answers 404, which the form reads as "no custom regions", so this can deploy first).

For the human reviewer

  1. Requirement. The form's region entry changes shape from { regionName, latencyDescription } to { regionId, quantity? }, with the name and latency selects becoming a view over an id-keyed lookup. The planning review rejected my first draft (a custom region as a virtual catalog row, keyed by name) because a custom region's name may legitimately equal a catalog family or Location tag; keying by id is what central-manager stores anyway. Cost: every consumer of the entry (validation, price, resources panel, defaults, drafts, submit) was touched. Reversible only by reverting the form.
  2. A member keeps a custom region staff placed, and cannot remove or swap it. The picker is disabled and the remove button hidden for that row (RegionFormInputs.tsx) — only while the server's plans carry it, so a member retrying a failed cluster (whose plans arrive as a draft, not a route cluster) can swap the row out — and validation refuses a custom region the server's plans don't already carry. The alternative — let the member drop it — hands a customer control over a placement an FDE designed and sized. Client-side only; central-manager's staff gate is the real boundary. One-line change if you want members to be able to remove one.
  3. Editing refuses to open when a stored region id cannot be resolved. An edit that silently dropped an unresolvable plan would send a plan list central-manager reads as "remove this region". The form shows Region Unavailable (or Custom Regions Unavailable when the /OrganizationRegion fetch itself failed, with retry copy) instead. Round 1 found a restored billing-redirect draft bypassed this; the check now keys on the server's plans, never the draft. The cost is that staff cannot edit such a cluster at all until the row is restored; the alternative (pass unknown plans through untouched) needs central-manager to accept plans it cannot resolve, which it does not.
  4. A custom region is on a cluster once; units say how many copies — central-manager keeps one plan per oreg- id, so the picker disables a custom region already on another row ("change its units") rather than letting validation refuse a duplicate. Defining one from the form never edits the rows (decided 2026-10-05): it lands in the picker, so staff can define several and then choose.
  5. Custom regions skip the plan's allowedRegionIds and one-region rules on the client. (A custom region with no datacenters on the organization's provider is hidden from the picker and refused by validation and at submit, since it would deploy nothing.) (A custom region with no datacenters on the organization's provider is hidden from the picker and refused by validation and at submit, since it would deploy nothing.) Those rules describe the catalog (stringsShareAPrefix on catalog ids); central-manager has no allowedRegionIds enforcement and decides what a custom region may do. Consequence flagged in review: a staff user can add a custom region to a free plan without the client objecting. Kept: staff-only, and the entitlement policy belongs server-side. Say the word and the client can apply the one-region rule to custom regions too.
  6. Provider for the shape comes from organization.channel === 'Akamai' → linode, otherwise GCP — the same rule the form already uses for cloudInstanceTypes. A custom region's placement is per provider, and the shape shown (datacenters per unit, instance count, price) follows this choice. If central-manager ever derives the provider differently, the displayed shape is wrong; cheap to change once the server exposes it.
  7. Custom-region price is priceUsd × ⌈datacenters per unit / 2⌉ × units, because that is what central-manager mints: a purchased block per instance pair, rounded up — the catalog's own purchasedBlockMultiplier = instanceCount / 2 rule. Staff never enter a block count (decided 2026-10-05: the first cut had a blocksPerUnit field, since removed from the admin form, table, types and payloads; central-manager#892 derives it in its resolver). The catalog formula (priceUsd × instanceCount / 2) is unchanged.
  8. No standalone admin page (David, 2026-10-07). The first cut added /admin/custom-regions; the list-and-edit surface moves to the organization's admin tab in a separate PR. The modal, queries and form schema stay under src/features/admin/organizationRegions/ for the cluster form's inline define and for that tab to reuse.
  9. Not in this PR, by scope. The cluster list resolves a custom region's name for its region filter; the overview does not yet show a custom region's units or shape (the usage page names the region through central-manager's cohort key). Follow-up, not a defect. A failed cluster write still dismisses its progress toast without surfacing the error — pre-existing, lines untouched.

Product and architecture tour

One region entry, two tables

How does the cluster form hold a catalog tier and a custom region in the same list?

Before After
An entry was { regionName, latencyDescription }; the catalog row (and its id) was looked up at submit. An entry is { regionId, quantity? }; buildRegionLookup resolves catalog rows and the organization's custom regions into one id-keyed map that validation, price, the resources panel, display and submit all read.

The region-name and latency selects are a view over the lookup: changing either writes a new regionId; they never hold state of their own.
quantity is present only for an oreg- entry and is stripped for catalog tiers at submit; central-manager refuses a quantity on a catalog region.
Two entries conflict when their cohort keys match — a catalog family by name, a custom region by id — mirroring regionCohortKey in central-manager.

Example: a custom region with two units

Staff pick EU edge (shape fr-par ×2 · it-mil, so ⌈3 / 2⌉ = 2 blocks per unit) on a Medium plan and set units to 2. The row shows "6 instances", the resources panel scales to the quantity, and the price line adds priceUsd × 2 × 2.

Outcome: the create payload carries { planId, regionId: 'oreg-…', quantity: 2, autoRenew: true } next to any catalog entries, which carry no quantity.

Who may do what

A member works the catalog as before. Staff holding the matching cluster permission additionally see the organization's active custom regions in the picker, set units, and scale on the same id; with region:write as well they can define a new one inline, which is two requests (the row, then the cluster). The table after the tour lists the matrix.

The member gate is derived from the server's cluster.plans, never from a restored draft. It is a client courtesy; central-manager's staff gate is the authorisation boundary.
Custom regions bypass the plan's allowedRegionIds and single-region rules on the client. Those rules describe the catalog; central-manager decides what a custom region may do.

Editing never loses a region

What happens when a stored region cannot be resolved?

Opening the edit form

  1. Fetch — catalog (per deployment) and /OrganizationRegion/?organizationId= (a 404 reads as none; any other failure reads as none too, but is remembered).
  2. Resolve — every stored plan id through the lookup; unresolved ids are collected, not dropped.
  3. Reconcile a draft — a billing-return draft supplies the values, but the unresolved check still keys on the server's plans.
  4. Refuse or open — any unresolved id refuses the form (Region Unavailable, or Custom Regions Unavailable with retry copy after a failed fetch), except in version-only mode, which never sends region plans.

Defining a custom region from the cluster form

The modal saves the row and invalidates the list; the new region then appears in the picker's Custom regions group and staff choose which rows deploy it — defining never edits the rows, so several can be defined before any is picked.

sequenceDiagram
    participant Staff
    participant Form as Cluster form
    participant Modal
    participant CM as central-manager
    Staff->>Form: Define custom region
    Form->>Modal: open
    Modal->>CM: POST OrganizationRegion
    CM-->>Modal: oreg-id
    Modal->>Form: onSaved(region)
    Form->>CM: GET OrganizationRegion list
    CM-->>Form: rows incl. new id
    Form->>Staff: listed under Custom regions
    Staff->>Form: pick it on a row, set units
Loading
  • Should a member be allowed to remove a custom region staff placed, or is read-only the right default for a placement an FDE sized?
  • Should the client apply the free plan's single-region rule to custom regions as well, or stay out of that policy entirely?

Permissions at a glance

Actor Catalog tiers Custom regions
Organization member pick, add, remove, swap sees one staff already placed, read-only; cannot add, swap or remove it
Staff with cluster:create / cluster:update as a member pick from the organization's active rows, set units, scale on the same id
Staff with region:write as well — also define a new one inline from the form (listing and editing them lives on the organization's admin tab, a separate PR)

Changes

Types and API. src/integrations/api/api.patch.d.ts overlays OrganizationRegion (+ payload/patch shapes) and quantity on both the read (RegionPlan) and write (ClusterUpsertRegionPlan) plan shapes, since regenerating api.gen.d.ts was rejected earlier (it rewrites 10k lines). src/features/admin/organizationRegions/queries/getOrganizationRegions.ts lists an organization's rows (404 → [] for the rollout) and reads one by id with its referencing clusters; mutations/useOrganizationRegionMutations.ts creates and patches changed fields only, because central-manager 409s any write to a frozen field while a live cluster references the row.

Custom-region module (no page of its own — see the ledger). components/OrganizationRegionFormModal.tsx is the create/edit form (per-provider datacenter multi-selects that allow repeats, with a per-datacenter count summary; fallback pool that must cover every listed datacenter, checked only when the shape or pool is being written; active), with the frozen fields disabled once the by-id read shows live clusters. OrganizationRegionFormSchema.ts holds the zod schema and the toFormValues/toCreatePayload/toPatch mapping. regions/components/DatacenterCountSummary.tsx is shared with the catalog region form, which regions/components/RegionFormModal.tsx now renders under both of its pickers.

Cluster form. upsertClusterSchema.ts defines RegionPlanEntrySchema (regionId, optional integer quantity 1–50). lib/regionLookup.ts resolves catalog tiers and custom regions into one ResolvedRegion keyed by id (buildRegionLookup, regionAtQuantity for the resources panel, regionCohortKey matching central-manager's grouping, describeShape). lib/regionPlanDefaults.ts maps a cluster's stored plans to entries and reports unresolved ids rather than dropping them, and migrates pre-id drafts through the catalog; lib/calculateDefaultDeploymentPerformanceAndRegionPlans.ts picks the default entry by catalog id. upsert/index.tsx fetches the organization's custom regions, builds defaults (refusing on unresolved ids, with the failed-fetch variant), and passes the staff gates (cluster:create|update to use, plus region:write to define). ClusterForm.tsx validates by id (cohort duplicates, member gate against the server's plans, units required, two-instance floor for custom regions, catalog plan restrictions), prices by blocks for custom regions, carries a catalog choice across a deployment switch by name and latency tier, re-validates when the lookup changes, and submits quantity only for custom regions; ClusterDetails.tsx threads the lookup, organization id and staff gates down to the regions block. ClusterRegions.tsx offers the next catalog family — or, once those are exhausted, an existing custom region — and hosts the inline Define custom region modal. components/RegionFormInputs.tsx renders the region select (catalog group + custom group) and either the latency select or the units field with its instance count.

Select descriptions (folded in from the pricing epic). fields/ClusterDeploymentDescription.tsx and fields/ClusterPerformanceDescription.tsx truncate an option's description with an ellipsis instead of running it under the chevron — the same change as f1b45be6 on epic/pricing-updates, cherry-picked so it reaches stage (a patch-identical commit, so the epic's next rebase drops its copy).

Cluster list. ClustersList.tsx merges custom-region names into the region-name map the list filter uses.

Verification

npx tsc -p tsconfig.app.json --noEmit clean; pnpm lint clean; pnpm format applied; pnpm test 388 files / 3600 tests pass (11 skipped, baseline). New or extended tests: lib/regionLookup.test.ts (id prefix, provider shape, lookup over both tables, cohort keys, quantity scaling, shape description), lib/regionPlanDefaults.test.ts (stored plans → entries incl. default units, unresolved ids reported, self-hosted plans skipped, legacy draft migration), upsertClusterSchema.test.ts (id required, quantity bounds), organizationRegions/OrganizationRegionFormSchema.test.ts (schema bounds, form/payload/patch mapping incl. changed-fields-only and null fallback), admin/__tests__/adminShellVisibility.test.ts (one more narrow-role case), ClustersList.test.tsx (custom-region name in the filter).

E2E (anon lane, mocked central-manager, local vite): tests/cluster-form.anon.spec.ts — all 7 tests ran clean under a single worker (paid creation through billing with the regionPlans: [{ planId, regionId: 'us-1', autoRenew }] payload, partial version upgrade, light/dark layouts at 1440/1024/390, self-hosted creation, hosted edit submitting only capacity, enterprise payment review); the runner hung at teardown on this machine both times (after the last test, no summary line), so the per-test lines and screenshots are the evidence. The spec's catch-all route 404s /OrganizationRegion/, which is the rollout path.

Live, on the local Fabric rig with central-manager from #892: the built SPA from this branch is served by the rig CM and its bundle carries the new form code; GET /OrganizationRegion/?organizationId=… answers 200 [] through the Studio client. The signed-in UI walk (picker group, units field, inline define) was not driven by me — Studio sign-in on the rig needs a human session — so it is owed before merge.

🤖 Generated with Claude Code

Related PRs: #1776 overlaps
Complexity: complicated

Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; blocked=cursor-grok(not-installed); declined=cursor-composer,cursor-kimi,cursor-muse; rounds=5; full=1 @ 9918e7c

Review-Attention: deep ~30m (critical: OrganizationRegionFormSchema.ts, upsertClusterSchema.ts) @ 9918e7c

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces organization-owned custom regions, allowing staff to define and manage custom datacenter placements and integrate them into the cluster creation and update flows. It adds dedicated administration routes, schemas, and modals for custom regions, alongside utilities to resolve region plans and migrate legacy drafts. Feedback on the changes highlights a correction in Zod's schema validation where passing an error option directly to z.number() is unsupported, and suggests safer handling of controlled numeric inputs in React to prevent uncontrolled input warnings when clearing values.

Comment on lines +16 to +19
blocksPerUnit: z.number({ error: 'Enter a whole number' }).int('Must be a whole number').min(1, 'Must be at least 1').max(
10,
'At most 10 blocks per unit',
),

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.

medium

In Zod, passing { error: '...' } to primitive schemas like z.number() is not supported and will be ignored. The correct options to customize the error messages are invalid_type_error and required_error. Without these, Zod will fall back to default error messages (e.g., "Expected number, received undefined") when the input is empty or invalid.

	blocksPerUnit: z.number({
		invalid_type_error: 'Enter a whole number',
		required_error: 'Enter a whole number',
	}).int('Must be a whole number').min(1, 'Must be at least 1').max(
		10,
		'At most 10 blocks per unit',
	),

Comment on lines +285 to +286
value={Number.isFinite(field.value) ? field.value : ''}
onChange={(e) => field.onChange(e.target.valueAsNumber)}

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.

medium

When rendering controlled numeric inputs in React, use Number.isFinite(field.value) ? field.value : '' to safely handle NaN, undefined, null, and Infinity in a single predicate, preventing React's uncontrolled input warnings. Additionally, map NaN to undefined when the input is cleared to match the form schema expectations.

value={Number.isFinite(field.value) ? field.value : ''}
onChange={(e) => {
	const val = e.target.valueAsNumber;
	field.onChange(Number.isNaN(val) ? undefined : val);
}}
References
  1. When rendering controlled numeric inputs in React, use Number.isFinite(field.value) ? field.value : '' to safely handle NaN, undefined, null, and Infinity in a single predicate, preventing React's uncontrolled input warnings.

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 66.77% 10166 / 15224
🔵 Statements 66.92% 10846 / 16205
🔵 Functions 60.13% 2617 / 4352
🔵 Branches 61.49% 7762 / 12623
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/features/admin/organizationRegions/OrganizationRegionFormSchema.ts 95.23% 90% 100% 94.73% 56
src/features/admin/organizationRegions/components/OrganizationRegionFormModal.tsx 1.12% 0% 0% 1.2% 55-336
src/features/admin/organizationRegions/mutations/useOrganizationRegionMutations.ts 14.28% 100% 0% 14.28% 6-19, 26-36
src/features/admin/organizationRegions/queries/getOrganizationRegions.ts 25% 12.5% 25% 21.42% 12-27, 31, 36-45
src/features/admin/regions/components/DatacenterCountSummary.tsx 0% 0% 0% 0% 7-13
src/features/admin/regions/components/RegionFormModal.tsx 1.42% 0% 0% 1.61% 39-383
src/features/clusters/ClustersList.tsx 96.87% 89.18% 93.75% 96.77% 144
src/features/clusters/upsert/ClusterDetails.tsx 0% 0% 0% 0% 73-276
src/features/clusters/upsert/ClusterForm.tsx 0% 0% 0% 0% 83-714
src/features/clusters/upsert/ClusterRegions.tsx 0% 0% 0% 0% 51-159
src/features/clusters/upsert/index.tsx 0% 0% 0% 0% 44-357
src/features/clusters/upsert/upsertClusterSchema.ts 100% 100% 100% 100%
src/features/clusters/upsert/components/RegionFormInputs.tsx 0% 0% 0% 0% 65-328
src/features/clusters/upsert/fields/ClusterDeploymentDescription.tsx 16.66% 0% 0% 16.66% 28-60
src/features/clusters/upsert/fields/ClusterPerformanceDescription.tsx 0% 0% 0% 0% 28-65
src/features/clusters/upsert/lib/calculateDefaultDeploymentPerformanceAndRegionPlans.ts 0% 0% 0% 0% 10-29
src/features/clusters/upsert/lib/regionLookup.ts 100% 95.83% 100% 100%
src/features/clusters/upsert/lib/regionPlanDefaults.ts 100% 96% 100% 100%
Generated in workflow #2061 for commit 9918e7c by the Vitest Coverage Report Action

DavidCockerill and others added 8 commits October 5, 2026 14:24
Adds the staff-only Custom regions page (/admin/custom-regions) over
central-manager's OrganizationRegion resource: pick an organization, list
its custom regions, create or edit one. A custom region is a placement
shape — the datacenters one unit occupies per provider, a repeated
datacenter meaning another instance there — plus an optional fallback pool
and blocks per unit. Fields central-manager freezes once a live cluster
deploys the region are disabled in the form; only `active` stays editable.

The catalog region form gains the same per-datacenter count summary so both
pickers read the same way. The API overlay types gain OrganizationRegion and
`quantity` on region plans; `regionLookup` resolves catalog tiers and custom
regions into one id-keyed shape the cluster form builds on next.

Refs #1778

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gions

The cluster form stored each region plan as a region name plus latency
description and resolved the id at submit; a custom region has neither, so
the form entry becomes `{ regionId, quantity? }`. An id-keyed lookup feeds
validation, price, the resources panel, display and submit; the region-name
and latency selects are now a view over it, and a selected custom region
swaps the latency select for a units field (datacenters per unit × units =
instances). Staff with the cluster permission see the organization's custom
regions in the picker and can define one inline; a member keeps a custom
region staff already placed but cannot add or swap one.

Edit defaults resolve every stored plan by id and refuse to open the form
when one cannot be resolved, since saving without it would remove that
region. Saved drafts keyed the old way migrate through the catalog. Switching
deployment carries a catalog choice over by name and latency tier where the
new catalog has a match. The cluster list resolves custom-region names for
its region filter.

Refs #1778

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ailed custom-region fetch

Addresses the first cross-model review round of #1778:

- A restored draft no longer bypasses the unresolved-region refusal: the
  check keys on the server's plans, so a custom region deleted while the
  user was at billing still blocks the edit instead of being dropped from
  the next save.
- The custom regions a member may keep come from the server's plans, not
  from form defaults a draft could have seeded.
- A failed /OrganizationRegion fetch (anything but the rollout 404) no
  longer strands every cluster form on "Loading…": catalog-only forms open,
  and an edit that needs a custom region is refused with a retry message.
- Price for a custom region follows what central-manager mints
  (blocksPerUnit × units), not the shape's instance count.
- "Add Additional Region Usage" offers an existing custom region once the
  catalog families are exhausted; "Define custom region" also requires
  region:write, which the modal's save needs.
- A region defined inline re-validates once its refetch lands, so the
  interim "no longer available" error clears itself.
- The fallback-coverage check in the custom-region modal only runs when the
  shape or pool is actually being written, so toggling `active` on a frozen
  region never waits on the Location catalog.
- The resources panel gets a memoized region view; narrating comments
  trimmed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ck version edits

Second review round of #1778:

- A saved draft keeps every id-keyed region entry, resolved or not, so a
  custom region the lookup lacks (fetch failure on the billing return,
  deleted row) is reported on its row by validation instead of vanishing
  from the draft and being replaced by a catalog default.
- The unresolved-region refusal no longer applies to version-only edits,
  which never send region plans.
- The allowed-region auto-select skips any custom-region id, resolved or
  pending, so an inline-defined region is not overwritten in its refetch
  window.
- The custom-region modal skips its fallback-coverage check while the
  Location catalog is absent (central-manager validates regardless), and
  its mutations opt out of the global error toast since failures land on
  the form.
- Comment corrections and trims.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A version edit never submits regions, so it no longer carries the empty
region placeholder the schema rejects, and submit skips region resolution
in that mode. Remaining narrating comments trimmed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A purchased block covers a pair of instances — every catalog row's
purchasedBlockMultiplier is instanceCount / 2 — so central-manager now
derives a custom region's blocks per unit from its datacenter list
(rounded up) instead of storing a staff-entered number. The admin form
and table drop the field, the API overlay types drop it, and the cluster
form prices a custom region by ⌈datacenters / 2⌉ × units, which is the
catalog's own rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nization's provider

Fourth review round of #1778: such a region (placed only on the other
provider) is hidden from the picker and, if already on the form, refused
by validation rather than submitted at zero instances and zero blocks.
A version-only edit skips region validation entirely, so a stale draft
cannot block it. The frozen-region note and the type doc no longer
mention blocks per unit; two exports with one consumer are internal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stom region

Fifth review round of #1778: "Try again" carries a failed cluster's plans
as a draft with no route cluster, so a staff-placed custom region was
locked on the row yet refused by validation. The row is now locked only
when the server's plans carry it. Payment review submits without
re-validating, so submit also refuses a custom region whose shape lost
every datacenter on the organization's provider while the user was away.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@DavidCockerill
DavidCockerill force-pushed the david/1778-custom-regions branch from 611ac90 to 16cc5c4 Compare October 5, 2026 18:25
DavidCockerill and others added 6 commits October 5, 2026 15:59
David: defining a custom region from the cluster form should not place it on
the deployment rows. It now only lands in the picker's Custom regions group
(with a toast), so staff can define several and then choose which rows deploy
them. A custom region can be on a cluster once — units say how many copies —
so the picker disables one that is already on another row instead of leaving
validation to refuse the duplicate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…one the picker cannot place

David defined two custom regions with only Linode datacenters for an
organization that deploys on GCP, and the cluster form hid them. The define
modal now fetches the organization, puts its provider's list first and
labels it, requires at least one datacenter there, and says so in the hint.
The cluster form lists such a region disabled with the reason instead of
hiding it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… form

The define modal is portaled out of the DOM but not out of the React tree,
so its submit event also reached the cluster form it opens from: the region
saved and, in the same click, the cluster form submitted and moved to the
payment step. The modal's submit now stops propagation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Clearing the field wrote undefined into the form, which validates on every
change, so the error showed before the new number could be typed. The box
now keeps a draft while editing: the form only learns a value it can hold,
and an empty box on blur falls back to the last one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… host it

David: the standalone admin page is the wrong home — custom regions belong
on the organization's own admin tab, which lands separately. The route,
rail item and page go; the modal, queries and form schema stay for the
cluster form's inline define and for that tab.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@DavidCockerill DavidCockerill changed the title feat(clusters): org-owned custom regions — admin page, picker group and units on the cluster form feat(clusters): org-owned custom regions — picker group and units on the cluster form Oct 7, 2026
DavidCockerill and others added 2 commits October 7, 2026 12:59
…an ellipsis

Same change as f1b45be on epic/pricing-updates, brought to stage so branches
cut from stage stop showing a deployment or performance description running
under the select's chevron.

(cherry picked from commit f1b45be)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… page

A custom region with no datacenters on the organization's provider still
lists disabled with the reason, but no longer sends staff to the
Custom regions admin page, which this PR no longer adds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Custom regions: per-org admin page and quantity on the cluster form

1 participant