I am working on a React and TypeScript form with two mutually exclusive generation modes:
text_to_logomust not contain a source image.2d_to_3dmust contain exactly one source, either a newly selectedFileor an existing asset id.
Both modes also select a model, aspect ratio, and output count. Model-to-mode and aspect-ratio compatibility comes from a capability response, so that part cannot be trusted as a compile-time constant.
The current UI state is intentionally flexible while the user edits, but the flat submission type can represent impossible combinations:
type Submission = {
mode: "text_to_logo" | "2d_to_3d";
files: File[];
existingAssetIds?: string[];
sourceTaskId?: string;
modelId: string;
ratio: string;
outputCount: number;
prompt: string;
};
For example, it permits text mode with a file, edit mode with both a file and an asset id, or edit mode with no source at all.
I am considering keeping a permissive draft type for the form, then validating it into a discriminated command only at the submission boundary:
type BaseCommand = {
modelId: string;
ratio: string;
outputCount: 1 | 2 | 3 | 4;
prompt: string;
};
type GenerationCommand =
| (BaseCommand & {
mode: "text_to_logo";
source?: never;
})
| (BaseCommand & {
mode: "2d_to_3d";
source:
| { kind: "upload"; file: File }
| { kind: "existing"; assetId: string; sourceTaskId?: string };
});
Runtime validation would still confirm that the chosen model supports the mode and ratio, especially after restoring saved form state when the server capability list may have changed.
Would you keep the editable form state and validated command as separate types, or use a reducer/state machine that makes invalid mode-and-source combinations impossible throughout the UI? Where would you normalize restored JSON state so an obsolete model or ratio cannot leak into the submission command?
I am leaning toward a permissive draft plus one pure parser returning Result<GenerationCommand, ValidationError[]>, because intermediate form states are naturally incomplete. I would appreciate arguments for or against that boundary.