/** * FieldEditDialog — Phase 2 (v0.5.0) + Phase 3B (v0.7.0). * * Sibling of FieldCreateDialog for editing an existing * tenant_field_definitions row. Pre-populates every input from the `field` * prop, applies the same snake_case validation and uniqueness rules (while * excluding the field's own current name), exposes a Save Changes button, * and adds a "Delete field" action guarded by `window.confirm`. * * Kept intentionally symmetric with FieldCreateDialog: identical reducer * shape, identical render-time reset pattern (keyed on `field.id` so opening * the dialog for a different field always re-seeds the form), identical * validation helper, identical styling. This mirror-symmetry keeps unit * tests short and means future UX tweaks can be applied in one pass across * both dialogs. * * Rename semantics: * Editing `field_name` here is metadata-only by default; an opt-in * disclosure (§3.B — Phase 3B) allows also renaming the physical column via * a dry-run → confirm → wet-run flow. Without the opt-in, the underlying * Postgres column keeps its old name and raw-SQL consumers continue to work * against the old identifier. Metadata-driven widgets (Form Builder / Page * Builder) resolve through `tenant_field_definitions.field_name` and pick up * the new name immediately. */ import type { RenamePhysicalColumnResult } from '../service.js'; import type { FieldType, TenantFieldDefinition } from '../types.js'; export interface FieldEditDialogPayload { field_name: string; field_type: FieldType; label: string; placeholder: string | null; is_required: boolean; } export interface FieldEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; /** Human-readable entity name; shown in the header. */ entityLabel: string; /** snake_case underlying name; shown as a hint under the header. */ entityName: string; /** * The field being edited. When null, nothing is rendered — parent * components should avoid mounting this dialog until they have a * concrete field to edit. */ field: TenantFieldDefinition | null; /** * Lowercased list of field names already defined on the entity * (including the field being edited — the uniqueness check excludes * the row's own current name automatically). */ existingFieldNames?: string[]; onSave: (payload: FieldEditDialogPayload) => void | Promise; onDelete: () => void | Promise; /** * Phase 3B — opt-in physical-column rename. When provided, a disclosure * appears under the form whenever `field_name` differs from the initial * value. Callers must support both dry-run (preview) and wet-run * (execute) modes via the `opts.dryRun` flag; the dialog calls dry-run * first to populate the confirmation step and wet-run only after the * user confirms. When omitted, the disclosure is hidden and rename * remains metadata-only. */ onRenamePhysical?: (newName: string, opts: { dryRun: boolean; }) => Promise; /** * Whether a physical column rename can actually succeed for THIS entity. * * The disclosure used to appear whenever `onRenamePhysical` was supplied and * the name had changed — which is not the same question. `rename_physical_column` * refuses unless the entity's table is registered in * `schema_builder_physical_tables`, and measured on production 2026-09-01 that * allowlist holds 0 rows while 0 of 45 `tenant_entities` resolve to a real * `public.` table at all. So the checkbox was offered on every field of * every entity and could not succeed for any of them: an inert control that * spent the user a confirmation step to reach a refusal. * * Undefined keeps the old behaviour, so an existing consumer is not silently * changed; pass `false` to hide the disclosure and let the rename take the * metadata path that actually works. */ physicalRenameAvailable?: boolean; } /** * Say what actually happened. * * Until 2026-09-01 every refusal rendered as "No physical table exists for this * entity; only the metadata name will change." Both halves were untrue for the * common case. `rename_physical_column` returned `no_physical_table` for ANY * unregistered entity, and its allowlist ships EMPTY — so that was the answer for * every entity, while the physical table usually did exist and was merely not * registered. The second half was untrue in every case: this branch returns * without performing any metadata rename, so nothing was renamed at all. * * The RPC now distinguishes the two (migration 20261101000000), and each message * below states the real situation and who can change it. A message that names the * wrong cause is worse than a vague one — it sends the user to fix something that * was never broken. */ export declare function refusalMessage(reason: string | undefined): string; export declare function FieldEditDialog({ open, onOpenChange, entityLabel, entityName, field, existingFieldNames, onSave, onDelete, onRenamePhysical, physicalRenameAvailable, }: FieldEditDialogProps): import("react").JSX.Element | null; //# sourceMappingURL=FieldEditDialog.d.ts.map