From 32165db44d17f5b14093d62bd03fb40cc9af4927 Mon Sep 17 00:00:00 2001 From: TJ Date: Mon, 3 Aug 2026 07:29:53 -0700 Subject: [PATCH 1/7] feat(site): use 4-column grid for base infra step --- site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx index 5d4a2b48d8bd2..aa9194b81463b 100644 --- a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx @@ -42,7 +42,7 @@ export const BaseInfraSelectStep: FC = ({ Select your infrastructure foundation. -
+
{bases.map((base) => ( Date: Mon, 3 Aug 2026 07:30:12 -0700 Subject: [PATCH 2/7] feat(site): update template card typography --- site/src/pages/TemplateBuilder/TemplateCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/pages/TemplateBuilder/TemplateCard.tsx b/site/src/pages/TemplateBuilder/TemplateCard.tsx index 72074bf42e2be..0d7c3428f6596 100644 --- a/site/src/pages/TemplateBuilder/TemplateCard.tsx +++ b/site/src/pages/TemplateBuilder/TemplateCard.tsx @@ -61,7 +61,7 @@ export const TemplateCard: React.FC = ({
-

+

{name} {official && ( <> @@ -71,7 +71,7 @@ export const TemplateCard: React.FC = ({ )}

-

+

{description}

From b6e08c1036fda8ed3bcddda1e925900d76fd357a Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Mon, 3 Aug 2026 14:41:37 +0000 Subject: [PATCH 3/7] feat(site): make base parameters body copy text-xs --- .../pages/TemplateBuilder/BaseTemplateParametersStep.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx index ca7b2266adc15..f8fd87ab8bf94 100644 --- a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx @@ -117,7 +117,7 @@ export const BaseTemplateParametersStep: FC< ); return ( - <> +
Configure base template Your base template requires customizations. @@ -134,11 +134,13 @@ export const BaseTemplateParametersStep: FC<
{prerequisites} @@ -146,6 +148,6 @@ export const BaseTemplateParametersStep: FC<
)} - +
); }; From 41ea0f3be9e618c25bc529d454a520ba33741469 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Mon, 3 Aug 2026 14:42:33 +0000 Subject: [PATCH 4/7] fix(site): use secondary color for bullet markers --- site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx index f8fd87ab8bf94..6687b22ed3ab8 100644 --- a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.tsx @@ -138,7 +138,7 @@ export const BaseTemplateParametersStep: FC< "[&_h2]:mt-6 [&_h2]:text-base [&_h2]:font-semibold", "[&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-semibold", "[&_p]:mb-3 [&_p]:text-content-secondary", - "[&_li]:text-content-secondary", + "[&_li]:text-content-secondary [&_li]:marker:text-content-secondary", "[&_a]:font-normal", "[&_code]:text-[length:inherit]", )} From 6930107d38dda3fe892fdc9879a334d04c9daf60 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Mon, 3 Aug 2026 18:16:10 +0000 Subject: [PATCH 5/7] fix(site): address review feedback - ConfigurationField: use text-xs for field descriptions consistently (fixes source of body-copy size mismatch instead of scoped override) - ModuleCard: apply same typography updates as TemplateCard - TemplateCard: shrink View details link to text-xs to match description - BaseInfraSelectStep: gate 4-up grid at xl instead of lg - BaseTemplateParametersStep: drop scoped wrapper and redundant marker: variant --- site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx | 2 +- .../pages/TemplateBuilder/BaseTemplateParametersStep.tsx | 6 +++--- site/src/pages/TemplateBuilder/ConfigurationField.tsx | 8 ++++---- site/src/pages/TemplateBuilder/ModuleCard.tsx | 6 +++--- site/src/pages/TemplateBuilder/TemplateCard.tsx | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx index aa9194b81463b..f99a5b8f25f9b 100644 --- a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx @@ -42,7 +42,7 @@ export const BaseInfraSelectStep: FC = ({ Select your infrastructure foundation. -
+
{bases.map((base) => ( + <> Configure base template Your base template requires customizations. @@ -138,7 +138,7 @@ export const BaseTemplateParametersStep: FC< "[&_h2]:mt-6 [&_h2]:text-base [&_h2]:font-semibold", "[&_h3]:mt-2 [&_h3]:mb-1 [&_h3]:text-sm [&_h3]:font-semibold", "[&_p]:mb-3 [&_p]:text-content-secondary", - "[&_li]:text-content-secondary [&_li]:marker:text-content-secondary", + "[&_li]:text-content-secondary", "[&_a]:font-normal", "[&_code]:text-[length:inherit]", )} @@ -148,6 +148,6 @@ export const BaseTemplateParametersStep: FC<
)} -
+ ); }; diff --git a/site/src/pages/TemplateBuilder/ConfigurationField.tsx b/site/src/pages/TemplateBuilder/ConfigurationField.tsx index b8e68550bf4ab..26a578ccc44a3 100644 --- a/site/src/pages/TemplateBuilder/ConfigurationField.tsx +++ b/site/src/pages/TemplateBuilder/ConfigurationField.tsx @@ -135,7 +135,7 @@ const SelectField: FC = ({ )} {description && ( -
+
{description}
)} @@ -185,7 +185,7 @@ const RadioField: FC = ({ )} {description && ( -
+
{description}
)} @@ -276,7 +276,7 @@ const SwitchField: FC = ({ {description && (
{description}
@@ -310,7 +310,7 @@ const SwitchGroupField: FC = ({ )} {description && ( -
+
{description}
)} diff --git a/site/src/pages/TemplateBuilder/ModuleCard.tsx b/site/src/pages/TemplateBuilder/ModuleCard.tsx index a6a0a1ad8c4be..bf793b414f091 100644 --- a/site/src/pages/TemplateBuilder/ModuleCard.tsx +++ b/site/src/pages/TemplateBuilder/ModuleCard.tsx @@ -62,7 +62,7 @@ export const ModuleCard: React.FC = ({
-

+

{name} {official && ( <> @@ -71,7 +71,7 @@ export const ModuleCard: React.FC = ({ )}

-

+

{description}

@@ -79,7 +79,7 @@ export const ModuleCard: React.FC = ({ View details diff --git a/site/src/pages/TemplateBuilder/TemplateCard.tsx b/site/src/pages/TemplateBuilder/TemplateCard.tsx index 0d7c3428f6596..6a987e2e47b90 100644 --- a/site/src/pages/TemplateBuilder/TemplateCard.tsx +++ b/site/src/pages/TemplateBuilder/TemplateCard.tsx @@ -78,7 +78,7 @@ export const TemplateCard: React.FC = ({ View details From 8c6c85f9f4224a4759120f2773f87108f8ca038b Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Mon, 3 Aug 2026 18:30:43 +0000 Subject: [PATCH 6/7] test(site): add stories for BaseInfraSelectStep and BaseTemplateParametersStep --- .../BaseInfraSelectStep.stories.tsx | 89 +++++++++++++++++++ .../BaseTemplateParametersStep.stories.tsx | 79 ++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx create mode 100644 site/src/pages/TemplateBuilder/BaseTemplateParametersStep.stories.tsx diff --git a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx new file mode 100644 index 0000000000000..312aabd06116a --- /dev/null +++ b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx @@ -0,0 +1,89 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { expect, fn, within } from "storybook/test"; +import type { TemplateBuilderBase } from "#/api/typesGenerated"; +import { BaseInfraSelectStep } from "./BaseInfraSelectStep"; + +function makeBase( + overrides: Pick, +): TemplateBuilderBase { + return { + icon: "", + os: "linux", + variables: [], + prerequisites: "", + ...overrides, + }; +} + +const bases: TemplateBuilderBase[] = [ + { + id: "aws-linux", + name: "AWS EC2 (Linux)", + description: "Provision AWS EC2 VMs as Coder workspaces", + }, + { + id: "aws-windows", + name: "AWS EC2 (Windows)", + description: "Provision AWS EC2 VMs as Coder workspaces", + }, + { + id: "azure-linux", + name: "Azure VM (Linux)", + description: "Provision Azure VMs as Coder workspaces", + }, + { + id: "docker", + name: "Docker Containers", + description: "Provision Docker containers as Coder workspaces", + }, +].map(makeBase); + +const meta: Meta = { + title: "pages/TemplateBuilder/BaseInfraSelectStep", + component: BaseInfraSelectStep, + args: { + selectedBaseId: null, + onSelectBase: fn(), + }, + parameters: { + queries: [ + { + key: ["templateBuilder", "bases"], + data: { bases }, + }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Selected: Story = { + args: { + selectedBaseId: "docker", + }, +}; + +export const Loading: Story = { + parameters: { + queries: [], + }, +}; + +// Verifies that every base is rendered as a radio option with the expected +// title and description text. +export const RendersAllBases: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const options = await canvas.findAllByRole("radio"); + await expect(options).toHaveLength(bases.length); + for (const base of bases) { + await expect(canvas.getByText(base.name)).toBeInTheDocument(); + await expect( + canvas.getAllByText(base.description).length, + ).toBeGreaterThan(0); + } + }, +}; diff --git a/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.stories.tsx b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.stories.tsx new file mode 100644 index 0000000000000..c76ee22fdd226 --- /dev/null +++ b/site/src/pages/TemplateBuilder/BaseTemplateParametersStep.stories.tsx @@ -0,0 +1,79 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { fn, within } from "storybook/test"; +import type { TemplateBuilderBase } from "#/api/typesGenerated"; +import { BaseTemplateParametersStep } from "./BaseTemplateParametersStep"; + +const prerequisites = `## Prerequisites + +### Workspace image + +The container image determines what tools, languages, and runtimes are +available in the workspace out of the box, so it has a major impact on the +developer experience. + +Some options to consider: + +- \`codercom/example-base:ubuntu\` (default): minimal and lightweight, but may + not include many tools developers expect by default +- \`codercom/example-universal:ubuntu\`: catch-all image with many languages + and tools available, but larger and slower to pull + +### Infrastructure + +The VM you run Coder on must have a running Docker socket and the \`coder\` +user must be added to the Docker group. +`; + +const base: TemplateBuilderBase = { + id: "docker", + name: "Docker Containers", + description: "Provision Docker containers as Coder workspaces", + icon: "", + os: "linux", + prerequisites, + variables: [ + { + name: "container_image", + type: "string", + description: + "Container image for workspaces. The image determines which tools and languages are available in the workspace by default. See the template README for guidance on choosing an image.", + default: { value: "codercom/example-base:ubuntu" }, + required: false, + sensitive: false, + }, + ], +}; + +const meta: Meta = { + title: "pages/TemplateBuilder/BaseTemplateParametersStep", + component: BaseTemplateParametersStep, + args: { + baseId: base.id, + values: {}, + onChangeValues: fn(), + }, + parameters: { + queries: [ + { + key: ["templateBuilder", "bases"], + data: { bases: [base] }, + }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +// Verifies that the field description and README body copy are rendered so +// the typography rules in this step have something to style. +export const RendersFieldAndPrerequisites: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await canvas.findByText(/Container image for workspaces/); + await canvas.findByRole("heading", { name: "Workspace image" }); + await canvas.findByRole("heading", { name: "Infrastructure" }); + }, +}; From 1806391114afe7dc31c50b27c8e48146df9404d5 Mon Sep 17 00:00:00 2001 From: Tracy Johnson Date: Tue, 4 Aug 2026 13:41:12 +0000 Subject: [PATCH 7/7] fix(site/src/pages/TemplateBuilder): restore lg grid tier and align module screens - Restore the lg:grid-cols-3 tier on base infra and module select grids so 1024-1279px keeps its previous density; xl:grid-cols-4 is now additive. - Match module screens to the base parameter typography scale: ModuleConfiguration 'No configuration required' notice, ModuleSettingsStep sensitive-variable notice, and ModuleSelectStep empty-state copy all move to text-xs. - Bring BaseTemplateCard heading in TemplateCustomizationsStep in line with TemplateCard/ModuleCard (text-sm font-bold). - Remove the two new step stories; step-level story coverage will be handled as a separate follow-up. --- .../BaseInfraSelectStep.stories.tsx | 89 ------------------- .../TemplateBuilder/BaseInfraSelectStep.tsx | 2 +- .../BaseTemplateParametersStep.stories.tsx | 79 ---------------- .../TemplateBuilder/ModuleConfiguration.tsx | 2 +- .../TemplateBuilder/ModuleSelectStep.tsx | 4 +- .../TemplateBuilder/ModuleSettingsStep.tsx | 2 +- .../TemplateCustomizationsStep.tsx | 2 +- 7 files changed, 6 insertions(+), 174 deletions(-) delete mode 100644 site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx delete mode 100644 site/src/pages/TemplateBuilder/BaseTemplateParametersStep.stories.tsx diff --git a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx deleted file mode 100644 index 312aabd06116a..0000000000000 --- a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.stories.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import { expect, fn, within } from "storybook/test"; -import type { TemplateBuilderBase } from "#/api/typesGenerated"; -import { BaseInfraSelectStep } from "./BaseInfraSelectStep"; - -function makeBase( - overrides: Pick, -): TemplateBuilderBase { - return { - icon: "", - os: "linux", - variables: [], - prerequisites: "", - ...overrides, - }; -} - -const bases: TemplateBuilderBase[] = [ - { - id: "aws-linux", - name: "AWS EC2 (Linux)", - description: "Provision AWS EC2 VMs as Coder workspaces", - }, - { - id: "aws-windows", - name: "AWS EC2 (Windows)", - description: "Provision AWS EC2 VMs as Coder workspaces", - }, - { - id: "azure-linux", - name: "Azure VM (Linux)", - description: "Provision Azure VMs as Coder workspaces", - }, - { - id: "docker", - name: "Docker Containers", - description: "Provision Docker containers as Coder workspaces", - }, -].map(makeBase); - -const meta: Meta = { - title: "pages/TemplateBuilder/BaseInfraSelectStep", - component: BaseInfraSelectStep, - args: { - selectedBaseId: null, - onSelectBase: fn(), - }, - parameters: { - queries: [ - { - key: ["templateBuilder", "bases"], - data: { bases }, - }, - ], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const Selected: Story = { - args: { - selectedBaseId: "docker", - }, -}; - -export const Loading: Story = { - parameters: { - queries: [], - }, -}; - -// Verifies that every base is rendered as a radio option with the expected -// title and description text. -export const RendersAllBases: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const options = await canvas.findAllByRole("radio"); - await expect(options).toHaveLength(bases.length); - for (const base of bases) { - await expect(canvas.getByText(base.name)).toBeInTheDocument(); - await expect( - canvas.getAllByText(base.description).length, - ).toBeGreaterThan(0); - } - }, -}; diff --git a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx index f99a5b8f25f9b..91cfc8d02106f 100644 --- a/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/BaseInfraSelectStep.tsx @@ -42,7 +42,7 @@ export const BaseInfraSelectStep: FC = ({ Select your infrastructure foundation. -
+
{bases.map((base) => ( = { - title: "pages/TemplateBuilder/BaseTemplateParametersStep", - component: BaseTemplateParametersStep, - args: { - baseId: base.id, - values: {}, - onChangeValues: fn(), - }, - parameters: { - queries: [ - { - key: ["templateBuilder", "bases"], - data: { bases: [base] }, - }, - ], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -// Verifies that the field description and README body copy are rendered so -// the typography rules in this step have something to style. -export const RendersFieldAndPrerequisites: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - await canvas.findByText(/Container image for workspaces/); - await canvas.findByRole("heading", { name: "Workspace image" }); - await canvas.findByRole("heading", { name: "Infrastructure" }); - }, -}; diff --git a/site/src/pages/TemplateBuilder/ModuleConfiguration.tsx b/site/src/pages/TemplateBuilder/ModuleConfiguration.tsx index 79a62c9027b81..85de9a946cdb6 100644 --- a/site/src/pages/TemplateBuilder/ModuleConfiguration.tsx +++ b/site/src/pages/TemplateBuilder/ModuleConfiguration.tsx @@ -67,7 +67,7 @@ export const ModuleConfiguration: React.FC = ({ ) : ( -
+
No configuration required.
diff --git a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx index a5a04e123492c..86668f4b3a985 100644 --- a/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSelectStep.tsx @@ -244,7 +244,7 @@ export const ModuleSelectStep: FC = ({ -
+
{visibleModules.length ? ( visibleModules.map((m) => ( = ({ ) : (
-

+

{doesBaseTemplateHaveModules ? "No module matched your search" : "No modules available for this base template"} diff --git a/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx b/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx index a156884adbf4b..4c36def8b0c47 100644 --- a/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx +++ b/site/src/pages/TemplateBuilder/ModuleSettingsStep.tsx @@ -162,7 +162,7 @@ export const ModuleSettingsStep: FC = ({ /> {sensitiveVars.length > 0 && ( -

+

{sensitiveVars.map((v) => ( diff --git a/site/src/pages/TemplateBuilder/TemplateCustomizationsStep.tsx b/site/src/pages/TemplateBuilder/TemplateCustomizationsStep.tsx index 95a2aaf5faede..22885bef3902e 100644 --- a/site/src/pages/TemplateBuilder/TemplateCustomizationsStep.tsx +++ b/site/src/pages/TemplateBuilder/TemplateCustomizationsStep.tsx @@ -181,7 +181,7 @@ const BaseTemplateCard: FC<{ base: SelectedBaseMeta }> = ({ base }) => { return (

{base.iconUrl && } -

{base.name}

+

{base.name}

Preset based on base template