mirror of
https://github.com/coder/coder.git
synced 2026-10-31 02:43:21 +08:00
Closes [DEVEX-586](https://linear.app/codercom/issue/DEVEX-586/auto-fill-customization-fields-from-selected-base-template). The Template Builder wizard's final **Customizations** step rendered empty inputs for ID, Display name, Description, and Icon. This seeds those fields with sensible defaults derived from the selected base template, while keeping every field editable. ## Changes All frontend, in `site/src/pages/TemplateBuilder/wizardState.ts`: - `SelectedBaseMeta` now carries `description`, mapped in `toSelectedBaseMeta`. - New pure helper `baseCustomizationDefaults(base)` maps the base to `{ name: base.id, displayName: base.name, description, icon }`. - `SET_BASE` seeds the four customization fields when the base changes (still clearing base variable values). Re-selecting the same base preserves user edits. - `initWizardState` seeds the same defaults for the `?base=` deeplink entry path. - `RESET_CUSTOMIZATIONS` (fired on back-navigation) now resets only organization/provisioner state, so auto-filled values survive stepping back and forth. No change was needed in `TemplateCustomizationsStep.tsx`; it already binds to these state fields, so seeded values render and stay editable. Existing placeholders remain as the fallback when a base value is empty (e.g. `scratch`). ### Default mapping | Form field | State key | Source | | ------------ | ------------- | ------------------ | | ID | `name` | `base.id` | | Display name | `displayName` | `base.name` | | Description | `description` | `base.description` | | Icon | `icon` | `base.icon` | `base.id` is used for the ID field because base ids are already valid template slugs (`docker`, `aws-linux`, ...). Icon values are already served asset paths (`/icon/*`, `/emojis/*`), so no lookup map is needed. ## Testing - `wizardState.test.ts`: 27 passing, including new coverage for seeding, base-change re-seed, same-base edit preservation, `RESET_CUSTOMIZATIONS`, `initWizardState`, `toSelectedBaseMeta`, and `baseCustomizationDefaults`. - `biome check` clean; `tsc --noEmit` clean. <img width="1043" height="593" alt="Screenshot 2026-07-15 at 2 17 19 PM" src="https://github.com/user-attachments/assets/25620df4-b020-4430-b5f3-9ce8b7b9f379" /> <details> <summary>Implementation plan</summary> # DEVEX-586: Auto-fill customization fields from selected base template ## Goal In the Template Builder wizard, the final **Customizations** step currently renders empty inputs for Display name, Description, ID, and Icon. Pre-populate these with sensible defaults derived from the selected base template, while keeping every field editable. Organization is already auto-selected when a single org is available, so it is out of scope beyond leaving it untouched. Source: Linear DEVEX-586 (Ben Potter): "Would love if all these options ... were auto-filled and generated and can be edited versus the user manually filling it out." Fields called out: display name, description, ID, and icon. ## Current behavior (findings) Frontend lives in `site/src/pages/TemplateBuilder/`. - `TemplateCustomizationsStep.tsx` renders the four fields bound to `state.displayName`, `state.description`, `state.name` (the "ID" field), and `state.icon`. All start empty (`initialWizardState`). - `wizardState.ts` holds the state, the `wizardReducer`, and the `SelectedBaseMeta` UI type. - `SelectedBaseMeta` did not carry `description`. - `toSelectedBaseMeta(base)` maps the API `TemplateBuilderBase` into `SelectedBaseMeta`. - `SET_BASE` set the base and cleared `baseVariableValues` only when the base id changed; customization fields were left empty. - `RESET_CUSTOMIZATIONS` (dispatched by `handleBack`) blanked org/provisioner plus `name`, `displayName`, `description`, `icon`. - `initWizardState(preselectedBase)` seeded `baseTemplateId` and `selectedBase` for deeplinks but left customization fields empty. - API type `TemplateBuilderBase` exposes `id`, `name`, `description`, `icon`, `os`, `variables`, `prerequisites`. - Backend (`coderd/templatebuilder_handler.go`) builds each base from the built-in `TemplateExample`: `id = ex.ID`, `name = ex.Name`, `description = ex.Description`, `icon = ex.Icon`. - Base IDs are valid template names (lowercase, hyphen-separated): `docker`, `kubernetes`, `aws-linux`, `aws-windows`, `gcp-linux`, `gcp-windows`, `azure-linux`, `digitalocean-linux`, `scratch`. ## Default mapping | Form field | State key | Default source | | --- | --- | --- | | ID (required) | `name` | `base.id` | | Display name | `displayName` | `base.name` | | Description | `description` | `base.description` | | Icon | `icon` | `base.iconUrl` (`base.icon`) | | Organization | `organizationId` | unchanged (already auto-selected) | Rationale for `name = base.id`: base ids are guaranteed valid template slugs, whereas slugifying the human display name is lossy and can collide. The field stays editable. ## Icon default: confirmed, no map needed The API already returns normalized, served asset paths in `base.icon` (e.g. `/icon/docker.png`, `/icon/aws.svg`, `/emojis/1f4e6.png` for `scratch`), the same values `CreateTemplatePage` assigns when creating from a built-in example. `IconField` accepts any URL/path, so every base renders correctly. A base-id-to-icon map is unnecessary. ## Implementation Frontend-only, in `wizardState.ts`: 1. Add `description?: string` to `SelectedBaseMeta`; map it in `toSelectedBaseMeta`. 2. Add pure helper `baseCustomizationDefaults(base)` returning `{ name, displayName, description, icon }`. 3. `SET_BASE`: on base change, spread the defaults alongside the `baseVariableValues: {}` reset; on same-base re-selection, preserve existing values. 4. `initWizardState(preselectedBase)`: spread defaults into the returned state. 5. Narrow `RESET_CUSTOMIZATIONS` to reset only `organizationId` and `hasProvisioners`. 6. `toCreateTemplateRequest` already sends the fields, so no payload change. ## Tests Reducer unit tests in `wizardState.test.ts` for seeding, base-change re-seed, same-base edit preservation, `RESET_CUSTOMIZATIONS`, `initWizardState`, `toSelectedBaseMeta` (description carried), and `baseCustomizationDefaults`. ## Scope / non-goals - No backend changes; the API already returns all needed fields. - Organization auto-selection is unchanged. - No slug transformation of display names (base id is used directly). - No base-id-to-icon map. </details> --- *Opened by Coder Agents on behalf of @jeremyruppel.*