feat(site): display prerequisites in base template step (#26524)

Display the base template prerequisites in the Template Builder wizard.
Stacked on #26523.

The `base-parameters` wizard step now renders the prerequisites markdown
(served by the backend `prerequisites` field) below the variable
configuration fields using `MemoizedMarkdown`. The step is shown when
the base has parameters **or** prerequisites, so Docker (no parameters,
has prerequisites) now shows this step.

## Changes

- `SelectedBaseMeta` gains `hasPrerequisites` boolean
- `toSelectedBaseMeta()` populates it from `base.prerequisites`
- `base-parameters` step skip logic: show when base has parameters or
prerequisites
- `BaseTemplateParametersStep`: render full prerequisites markdown as-is
(headings intact)
- Updated all test fixtures with the new field

*Generated with the assistance of an AI coding agent. Reviewed by
@jeremyruppel.*

Relates to https://linear.app/codercom/issue/DEVEX-446
This commit is contained in:
Jeremy Ruppel
2026-06-23 11:57:28 -04:00
committed by GitHub
parent 0aa14c7d45
commit 47adfb3f28
7 changed files with 56 additions and 9 deletions
@@ -20,6 +20,7 @@ function toSelectedBaseMeta(base: TemplateBuilderBase): SelectedBaseMeta {
os: base.os,
hasParameters:
base.variables.length > 0 && base.variables.some((v) => !v.sensitive),
hasPrerequisites: Boolean(base.prerequisites?.length),
};
}
@@ -5,6 +5,7 @@ import type {
TemplateBuilderBasesResponse,
TemplateBuilderModuleVariable,
} from "#/api/typesGenerated";
import { MemoizedMarkdown } from "#/components/Markdown/Markdown";
import type { ConfigurationFieldDefinition } from "./ConfigurationField";
import { TemplateConfiguration } from "./TemplateConfiguration";
@@ -89,6 +90,7 @@ export const BaseTemplateParametersStep: FC<
const { data } = useQuery(templateBuilderBases());
const base = data?.bases.find((b) => b.id === baseId);
const variables = base?.variables.filter((v) => !v.sensitive) ?? [];
const prerequisites = base?.prerequisites ?? "";
const handleChange = (name: string, value: string) => {
onChangeValues({ ...values, [name]: value });
@@ -105,6 +107,12 @@ export const BaseTemplateParametersStep: FC<
iconUrl={base?.icon}
detailsUrl={detailsUrl(baseId)}
fields={fields}
/>
>
{prerequisites && (
<div className="mt-6">
<MemoizedMarkdown>{prerequisites}</MemoizedMarkdown>
</div>
)}
</TemplateConfiguration>
);
};
@@ -1,16 +1,17 @@
import type { PropsWithChildren } from "react";
import { Link } from "#/components/Link/Link";
import {
ConfigurationField,
type ConfigurationFieldDefinition,
} from "./ConfigurationField";
type TemplateConfigurationProps = {
type TemplateConfigurationProps = PropsWithChildren<{
name: string;
description: string;
iconUrl?: string;
detailsUrl?: string;
fields?: ConfigurationFieldDefinition[];
};
}>;
export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
name,
@@ -18,6 +19,7 @@ export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
iconUrl,
detailsUrl,
fields,
children,
}) => {
return (
<section className="pt-4 px-4 pb-6 rounded bg-surface-secondary">
@@ -59,6 +61,7 @@ export const TemplateConfiguration: React.FC<TemplateConfigurationProps> = ({
))}
</div>
)}
{children}
</section>
);
};
@@ -35,6 +35,7 @@ describe("shouldSkip", () => {
id: "docker",
name: "Docker",
hasParameters: false,
hasPrerequisites: false,
},
});
expect(stepById("base-parameters").shouldSkip(noParams)).toBe(true);
@@ -46,6 +47,7 @@ describe("shouldSkip", () => {
id: "aws-linux",
name: "AWS Linux",
hasParameters: true,
hasPrerequisites: false,
},
});
expect(stepById("base-parameters").shouldSkip(withParams)).toBe(false);
@@ -137,6 +139,7 @@ describe("findNextVisibleIndex", () => {
id: "docker",
name: "Docker",
hasParameters: false,
hasPrerequisites: false,
},
});
// From base-infra (index 0), next visible should be module-select (index 2).
@@ -160,6 +163,7 @@ describe("findNextVisibleIndex", () => {
id: "aws-linux",
name: "AWS Linux",
hasParameters: true,
hasPrerequisites: false,
},
selectedModules: [
{
@@ -186,6 +190,7 @@ describe("findPrevVisibleIndex", () => {
id: "docker",
name: "Docker",
hasParameters: false,
hasPrerequisites: false,
},
});
// From module-select (index 2), prev visible should be base-infra (index 0).
@@ -220,6 +225,7 @@ describe("nearestVisible", () => {
id: "docker",
name: "Docker",
hasParameters: false,
hasPrerequisites: false,
},
};
// Index 1 (base-parameters) is skipped, nearest backward is 0 (base-infra).
+3 -1
View File
@@ -40,7 +40,9 @@ export const WIZARD_STEPS: readonly WizardStep[] = [
{
id: "base-parameters",
group: 1,
shouldSkip: (state) => !state.selectedBase?.hasParameters,
shouldSkip: (state) =>
!state.selectedBase?.hasParameters &&
!state.selectedBase?.hasPrerequisites,
},
{
id: "module-select",
@@ -26,6 +26,7 @@ describe("wizardReducer", () => {
id: "docker",
name: "Docker",
hasParameters: false,
hasPrerequisites: false,
},
},
]);
@@ -37,7 +38,12 @@ describe("wizardReducer", () => {
const state = reduce([
{
type: "SET_BASE",
base: { id: "docker", name: "Docker", hasParameters: true },
base: {
id: "docker",
name: "Docker",
hasParameters: true,
hasPrerequisites: false,
},
},
{
type: "SET_BASE_VARIABLES",
@@ -45,7 +51,12 @@ describe("wizardReducer", () => {
},
{
type: "SET_BASE",
base: { id: "aws-linux", name: "AWS Linux", hasParameters: true },
base: {
id: "aws-linux",
name: "AWS Linux",
hasParameters: true,
hasPrerequisites: false,
},
},
]);
expect(state.baseTemplateId).toBe("aws-linux");
@@ -56,7 +67,12 @@ describe("wizardReducer", () => {
const state = reduce([
{
type: "SET_BASE",
base: { id: "docker", name: "Docker", hasParameters: true },
base: {
id: "docker",
name: "Docker",
hasParameters: true,
hasPrerequisites: false,
},
},
{
type: "SET_BASE_VARIABLES",
@@ -64,7 +80,12 @@ describe("wizardReducer", () => {
},
{
type: "SET_BASE",
base: { id: "docker", name: "Docker", hasParameters: true },
base: {
id: "docker",
name: "Docker",
hasParameters: true,
hasPrerequisites: false,
},
},
]);
expect(state.baseVariableValues).toEqual({ image: "ubuntu" });
@@ -254,7 +275,12 @@ describe("wizardReducer", () => {
const state = reduce([
{
type: "SET_BASE",
base: { id: "docker", name: "Docker", hasParameters: true },
base: {
id: "docker",
name: "Docker",
hasParameters: true,
hasPrerequisites: false,
},
},
{
type: "SET_CUSTOMIZATION",
@@ -15,6 +15,7 @@ export type SelectedBaseMeta = {
iconUrl?: string;
os?: string;
hasParameters: boolean;
hasPrerequisites: boolean;
};
/**