diff --git a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx index 59535f57acc..b8c9b2fd3be 100644 --- a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx @@ -11,10 +11,7 @@ import { TemplateBuilderSubtitle, TemplateBuilderTitle, } from "#/pages/TemplateBuilder/TemplateBuilderHeader"; -import { - type ConfigurationFieldDefinition, - ConfigurationFieldLabel, -} from "./ConfigurationField"; +import type { ConfigurationFieldDefinition } from "./ConfigurationField"; import { defaultPlaceholder } from "./defaultPlaceholder"; import { TemplateConfiguration } from "./TemplateConfiguration"; @@ -40,7 +37,7 @@ function variableToField( error: boolean, ): ConfigurationFieldDefinition { const id = `base-var-${variable.name}`; - const label = ; + const label = variable.name; if (variable.type === "bool") { return { @@ -63,7 +60,7 @@ function variableToField( required: variable.required, placeholder: defaultPlaceholder(variable.default) ?? - (variable.required ? "Required" : "Optional"), + (variable.required ? "Required" : ""), field: { name: variable.name, id, diff --git a/site/src/pages/TemplateBuilder/ConfigurationField.tsx b/site/src/pages/TemplateBuilder/ConfigurationField.tsx index 4b00f78e715..263f5f7c497 100644 --- a/site/src/pages/TemplateBuilder/ConfigurationField.tsx +++ b/site/src/pages/TemplateBuilder/ConfigurationField.tsx @@ -1,5 +1,4 @@ import type { FC, PropsWithChildren, ReactNode } from "react"; -import type { TemplateBuilderModuleVariable } from "#/api/typesGenerated"; import { FormField } from "#/components/FormField/FormField"; import { Label } from "#/components/Label/Label"; import { RadioGroup, RadioGroupItem } from "#/components/RadioGroup/RadioGroup"; @@ -104,6 +103,7 @@ const TextField: FC = ({ description={description} required={required} placeholder={placeholder} + className="placeholder:text-content-disabled" /> ); @@ -123,15 +123,13 @@ const SelectField: FC = ({
{description && ( @@ -173,15 +171,13 @@ const RadioField: FC = ({
{description && ( @@ -298,15 +294,13 @@ const SwitchGroupField: FC = ({
{description && ( @@ -342,23 +336,3 @@ export const ConfigurationFieldContainer: FC = ({
); }; - -const OptionalIndicator: FC = () => { - return ( - <> - {" "} - (optional) - - ); -}; - -export const ConfigurationFieldLabel: FC<{ - variable: TemplateBuilderModuleVariable; -}> = ({ variable }) => { - return ( - <> - {variable.name} - {!variable.required && } - - ); -}; diff --git a/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx b/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx index 70ddd238fea..2b30c40b4dd 100644 --- a/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx @@ -10,12 +10,10 @@ import { TemplateBuilderSubtitle, TemplateBuilderTitle, } from "#/pages/TemplateBuilder/TemplateBuilderHeader"; -import { - type ConfigurationFieldDefinition, - ConfigurationFieldLabel, -} from "./ConfigurationField"; +import type { ConfigurationFieldDefinition } from "./ConfigurationField"; import { defaultPlaceholder } from "./defaultPlaceholder"; import { ModuleConfiguration } from "./ModuleConfiguration"; +import { getModuleFieldPlaceholder } from "./moduleFieldPlaceholders"; interface ModuleSettingsStepProps { baseId: string; @@ -38,7 +36,7 @@ function variableToField( error: boolean, ): ConfigurationFieldDefinition { const id = `mod-${moduleId}-${variable.name}`; - const label = ; + const label = variable.name; if (variable.type === "bool") { return { @@ -60,8 +58,9 @@ function variableToField( description: variable.description || undefined, required: variable.required, placeholder: + getModuleFieldPlaceholder(moduleId, variable.name) ?? defaultPlaceholder(variable.default) ?? - (variable.required ? "Required" : "Optional"), + (variable.required ? "Required" : ""), field: { name: variable.name, id, diff --git a/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.test.ts b/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.test.ts new file mode 100644 index 00000000000..23ab0760c6f --- /dev/null +++ b/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; +import { getModuleFieldPlaceholder } from "./moduleFieldPlaceholders"; + +describe("getModuleFieldPlaceholder", () => { + it("returns the override for known module/variable pairs", () => { + expect(getModuleFieldPlaceholder("codex", "model_reasoning_effort")).toBe( + "none, minimal, low, medium, high, or xhigh", + ); + expect(getModuleFieldPlaceholder("claude-code", "anthropic_api_key")).toBe( + "sk-ant-...", + ); + expect(getModuleFieldPlaceholder("claude-code", "model")).toBe( + "e.g. claude-sonnet-5", + ); + expect(getModuleFieldPlaceholder("git-clone", "base_dir")).toBe("$HOME"); + expect(getModuleFieldPlaceholder("git-clone", "branch_name")).toBe( + "Default branch", + ); + expect(getModuleFieldPlaceholder("dotfiles", "description")).toBe( + "e.g https://dotfiles.github.io or an SSH URL git@host:user/repo", + ); + }); + + it("returns undefined for a module without overrides", () => { + expect(getModuleFieldPlaceholder("code-server", "port")).toBeUndefined(); + }); + + it("returns undefined for an unmatched variable on a known module", () => { + expect( + getModuleFieldPlaceholder("codex", "does_not_exist"), + ).toBeUndefined(); + }); +}); diff --git a/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.ts b/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.ts new file mode 100644 index 00000000000..e3a73f14f44 --- /dev/null +++ b/site/src/pages/TemplateBuilder/moduleFieldPlaceholders.ts @@ -0,0 +1,42 @@ +/** + * Per-module placeholder text overrides for the template builder's + * module configuration fields. + * + * The API delivers each module's variable defaults verbatim from its + * `module.json`. Some of those defaults are prose that is more useful as + * documentation than as placeholder text (for example the dotfiles + * `description` default is a two-sentence explanation). This table lets + * us substitute short, actionable placeholder hints for a specific set + * of variables without changing the underlying module manifest. + * + * Lookup order used by the module configuration step: + * 1. Override in this table (if present). + * 2. Variable's `default` value. + * 3. `Required` when the variable is required, otherwise empty. + */ +const MODULE_FIELD_PLACEHOLDERS: Readonly< + Record>> +> = { + codex: { + model_reasoning_effort: "none, minimal, low, medium, high, or xhigh", + }, + "claude-code": { + anthropic_api_key: "sk-ant-...", + model: "e.g. claude-sonnet-5", + }, + "git-clone": { + base_dir: "$HOME", + branch_name: "Default branch", + }, + dotfiles: { + description: + "e.g https://dotfiles.github.io or an SSH URL git@host:user/repo", + }, +}; + +export function getModuleFieldPlaceholder( + moduleId: string, + variableName: string, +): string | undefined { + return MODULE_FIELD_PLACEHOLDERS[moduleId]?.[variableName]; +}