# Go Backend Spacing Primitives Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add fixed-size `SpaceX` and `SpaceY` primitives to the Go backend UI library, document them in the catalog, and cover them with direct UI and design-system tests.

**Architecture:** Implement spacing as two presentational templ primitives backed by a shared `SpacingStep` enum and helper normalization functions. Keep the component API explicit, keep layout behavior in CSS classes rather than inline styles, and update the catalog with inner wrappers so the preview container's existing `gap` does not distort the examples.

**Tech Stack:** Go, templ, generated templ Go files, static CSS, catalog page generation via `cmd/designsystem`, Go `testing`

---

## File Map

**Create**

- `go-backend/internal/web/ui/space.templ`
  - New `SpaceProps`, `SpaceX`, and `SpaceY` templ primitives.

**Modify**

- `go-backend/internal/web/ui/variants.go`
  - Add `SpacingStep` constants plus `normalizedSpacingStep`, `spaceXClass`, and `spaceYClass`.
- `go-backend/internal/web/ui/space_templ.go`
  - Generated templ output for the new spacing primitive.
- `go-backend/internal/web/ui/ui_test.go`
  - Add direct markup tests for default and explicit spacing steps.
- `go-backend/internal/web/ui/catalog/examples.go`
  - Add catalog examples that render real `SpaceX` and `SpaceY` markup inside zero-gap wrappers.
- `go-backend/internal/web/ui/catalog/pages.go`
  - Register the new `spacing` catalog page.
- `go-backend/internal/web/ui/catalog/catalog_test.go`
  - Add page registration and rendered markup assertions for the spacing page.
- `go-backend/cmd/designsystem/main_test.go`
  - Expect `spacing.html` in generated output.
- `go-backend/static/styles.css`
  - Add spacing utility classes and, if needed, tiny catalog helper wrappers for zero-gap preview rows/columns.
- `docs/design-system/index.html`
  - Generated design-system index with the spacing page link.
- `docs/design-system/tokens.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/buttons.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/badges.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/icon-buttons.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/inputs.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/form-fields.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/modals.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/tables.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/empty-states.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/cards.html`
  - Generated page with updated catalog navigation.
- `docs/design-system/spacing.html`
  - New generated design-system page for spacing.

**Commands / Generated Artifacts**

- `cd go-backend && just generate`
  - Regenerates `internal/web/ui/space_templ.go` and refreshes CSS build outputs.
- `cd go-backend && just design-system`
  - Regenerates `docs/design-system/*.html`.
- `cd go-backend && go test ./internal/web/ui ./internal/web/ui/catalog ./cmd/designsystem -count=1`
- `cd go-backend && go test ./internal/web/... ./cmd/designsystem -count=1`

## Task 1: Lock Down Spacing Behavior With Failing Tests

**Files:**
- Modify: `go-backend/internal/web/ui/ui_test.go`
- Modify: `go-backend/internal/web/ui/catalog/catalog_test.go`
- Modify: `go-backend/cmd/designsystem/main_test.go`

- [ ] **Step 1: Add failing direct UI tests for spacing markup**

Add two tests to `go-backend/internal/web/ui/ui_test.go` that lock down the default `md` size and an explicit `xl` size.

```go
func TestSpaceXRendersDefaultMediumMarkup(t *testing.T) {
	component := SpaceX(SpaceProps{})

	html := renderToString(t, component)

	for _, want := range []string{
		`aria-hidden="true"`,
		`class="ui-space-x ui-space-x-md"`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}

func TestSpaceYRendersExplicitExtraLargeMarkup(t *testing.T) {
	component := SpaceY(SpaceProps{Size: SpacingStepXL})

	html := renderToString(t, component)

	for _, want := range []string{
		`aria-hidden="true"`,
		`class="ui-space-y ui-space-y-xl"`,
	} {
		if !strings.Contains(html, want) {
			t.Fatalf("expected %q in %q", want, html)
		}
	}
}
```

- [ ] **Step 2: Extend stylesheet coverage assertions for the new classes**

In `TestSharedSemanticClassesExistInStylesheet`, add the spacing classes that must exist after implementation:

```go
for _, want := range []string{
	`.ui-space-x-md`,
	`.ui-space-y-md`,
} {
	if !strings.Contains(css, want) {
		t.Fatalf("expected stylesheet to contain %q", want)
	}
}
```

- [ ] **Step 3: Add failing catalog tests for the new page and generated site output**

Update `go-backend/internal/web/ui/catalog/catalog_test.go` and `go-backend/cmd/designsystem/main_test.go`.

```go
if _, ok := FindPage("spacing"); !ok {
	t.Fatalf("expected catalog page %q", "spacing")
}
```

```go
{slug: "spacing", want: []string{`ui-space-x`, `ui-space-y`}},
```

```go
for _, name := range []string{
	"spacing.html",
} {
	path := filepath.Join(outputDir, name)
	if _, err := os.Stat(path); err != nil {
		t.Fatalf("expected generated file %q: %v", path, err)
	}
}
```

- [ ] **Step 4: Run the focused tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/ui ./internal/web/ui/catalog ./cmd/designsystem -count=1`

Expected: FAIL with undefined identifiers such as `SpaceX`, `SpaceY`, `SpaceProps`, and missing `spacing` page expectations.

- [ ] **Step 5: Commit the failing test expectations**

```bash
git add go-backend/internal/web/ui/ui_test.go go-backend/internal/web/ui/catalog/catalog_test.go go-backend/cmd/designsystem/main_test.go
git commit -m "test: define spacing primitive expectations"
```

## Task 2: Implement The Spacing Primitive And CSS Contract

**Files:**
- Create: `go-backend/internal/web/ui/space.templ`
- Modify: `go-backend/internal/web/ui/variants.go`
- Modify: `go-backend/internal/web/ui/space_templ.go`
- Modify: `go-backend/internal/web/ui/ui_test.go`
- Modify: `go-backend/static/styles.css`

- [ ] **Step 1: Add the spacing type and helper functions**

Update `go-backend/internal/web/ui/variants.go` with a small shared scale and deterministic normalization:

```go
type SpacingStep string

const (
	SpacingStepXS SpacingStep = "xs"
	SpacingStepSM SpacingStep = "sm"
	SpacingStepMD SpacingStep = "md"
	SpacingStepLG SpacingStep = "lg"
	SpacingStepXL SpacingStep = "xl"
)

func normalizedSpacingStep(step SpacingStep) SpacingStep {
	switch step {
	case SpacingStepXS, SpacingStepSM, SpacingStepLG, SpacingStepXL:
		return step
	default:
		return SpacingStepMD
	}
}

func spaceXClass(step SpacingStep) string {
	return "ui-space-x ui-space-x-" + string(normalizedSpacingStep(step))
}

func spaceYClass(step SpacingStep) string {
	return "ui-space-y ui-space-y-" + string(normalizedSpacingStep(step))
}
```

- [ ] **Step 2: Create the templ primitive**

Create `go-backend/internal/web/ui/space.templ`:

```go
package ui

type SpaceProps struct {
	Size SpacingStep
}

templ SpaceX(props SpaceProps) {
	<span class={ spaceXClass(props.Size) } aria-hidden="true"></span>
}

templ SpaceY(props SpaceProps) {
	<div class={ spaceYClass(props.Size) } aria-hidden="true"></div>
}
```

- [ ] **Step 3: Add the CSS classes for base behavior and all spacing steps**

Append the spacing contract to `go-backend/static/styles.css` near the other `ui-` primitives:

```css
.ui-space-x {
  display: inline-block;
  flex-shrink: 0;
}

.ui-space-y {
  display: block;
}

.ui-space-x-xs { width: 0.25rem; }
.ui-space-x-sm { width: 0.5rem; }
.ui-space-x-md { width: 0.75rem; }
.ui-space-x-lg { width: 1rem; }
.ui-space-x-xl { width: 1.5rem; }

.ui-space-y-xs { height: 0.25rem; }
.ui-space-y-sm { height: 0.5rem; }
.ui-space-y-md { height: 0.75rem; }
.ui-space-y-lg { height: 1rem; }
.ui-space-y-xl { height: 1.5rem; }
```

- [ ] **Step 4: Regenerate templ output**

Run: `cd go-backend && just generate`

Expected: PASS with an updated `internal/web/ui/space_templ.go` and rebuilt static CSS assets.

- [ ] **Step 5: Run the focused component tests to verify they pass**

Run: `cd go-backend && go test ./internal/web/ui -run 'TestSpace|TestSharedSemanticClassesExistInStylesheet' -count=1`

Expected: PASS with the new spacing tests green.

- [ ] **Step 6: Commit the primitive implementation**

```bash
git add go-backend/internal/web/ui/variants.go go-backend/internal/web/ui/space.templ go-backend/internal/web/ui/space_templ.go go-backend/internal/web/ui/ui_test.go go-backend/static/styles.css
git commit -m "feat: add spacing primitives"
```

## Task 3: Register The Catalog Page And Refresh Generated Docs

**Files:**
- Modify: `go-backend/internal/web/ui/catalog/examples.go`
- Modify: `go-backend/internal/web/ui/catalog/pages.go`
- Modify: `go-backend/internal/web/ui/catalog/catalog_test.go`
- Modify: `go-backend/cmd/designsystem/main_test.go`
- Modify: `go-backend/static/styles.css`
- Modify: `docs/design-system/index.html`
- Modify: `docs/design-system/spacing.html`

- [ ] **Step 1: Register the new catalog page**

Update `go-backend/internal/web/ui/catalog/pages.go`:

```go
{
	Slug:        "spacing",
	Title:       "Spacing",
	Description: "Fixed horizontal and vertical spacer primitives for composing gaps between UI components.",
	Examples:    spacingExamples(),
},
```

- [ ] **Step 2: Add real spacing examples with zero-gap inner wrappers**

Add `spacingExamples()` to `go-backend/internal/web/ui/catalog/examples.go`. Use inner wrappers because `catalog-example-preview` already applies `gap: 0.75rem`.

```go
func spacingExamples() []Example {
	return []Example{
		{
			Title:       "Horizontal spacing",
			Description: "Use SpaceX to insert fixed horizontal gaps between inline or flex-row components.",
			Preview: componentFunc(func(ctx context.Context, w io.Writer) error {
				if _, err := io.WriteString(w, `<div class="catalog-spacing-row">`); err != nil {
					return err
				}
				if err := ui.Button(ui.ButtonProps{Label: "Précédent", Variant: ui.ButtonVariantNeutral, Size: ui.SizeMD, Type: "button"}).Render(ctx, w); err != nil {
					return err
				}
				if err := ui.SpaceX(ui.SpaceProps{Size: ui.SpacingStepLG}).Render(ctx, w); err != nil {
					return err
				}
				if err := ui.Button(ui.ButtonProps{Label: "Suivant", Variant: ui.ButtonVariantDefault, Size: ui.SizeMD, Type: "button"}).Render(ctx, w); err != nil {
					return err
				}
				_, err := io.WriteString(w, `</div>`)
				return err
			}),
			Snippet: `@ui.SpaceX(ui.SpaceProps{Size: ui.SpacingStepLG})`,
		},
		{
			Title:       "Vertical spacing",
			Description: "Use SpaceY to insert fixed vertical gaps between stacked blocks.",
			Preview: componentFunc(func(ctx context.Context, w io.Writer) error {
				if _, err := io.WriteString(w, `<div class="catalog-spacing-column">`); err != nil {
					return err
				}
				if err := ui.Card(ui.CardProps{Body: textComponent("Bloc 1")}).Render(ctx, w); err != nil {
					return err
				}
				if err := ui.SpaceY(ui.SpaceProps{Size: ui.SpacingStepMD}).Render(ctx, w); err != nil {
					return err
				}
				if err := ui.Card(ui.CardProps{Body: textComponent("Bloc 2")}).Render(ctx, w); err != nil {
					return err
				}
				_, err := io.WriteString(w, `</div>`)
				return err
			}),
			Snippet: `@ui.SpaceY(ui.SpaceProps{Size: ui.SpacingStepMD})`,
		},
	}
}
```

- [ ] **Step 3: Add tiny catalog-only helper wrappers**

Append these helpers to `go-backend/static/styles.css` so the previews show only spacer-controlled gaps:

```css
.catalog-spacing-row {
  align-items: center;
  display: flex;
  gap: 0;
}

.catalog-spacing-column {
  display: flex;
  flex-direction: column;
  gap: 0;
  width: 100%;
}
```

- [ ] **Step 4: Regenerate the design-system site**

Run: `cd go-backend && just design-system`

Expected: PASS with refreshed `docs/design-system/index.html` and a new `docs/design-system/spacing.html`.

- [ ] **Step 5: Run catalog and design-system tests**

Run: `cd go-backend && go test ./internal/web/ui/catalog ./cmd/designsystem -count=1`

Expected: PASS with the spacing page present in both rendered catalog output and generated static site output.

- [ ] **Step 6: Commit the catalog integration**

```bash
git add go-backend/internal/web/ui/catalog/examples.go go-backend/internal/web/ui/catalog/pages.go go-backend/internal/web/ui/catalog/catalog_test.go go-backend/cmd/designsystem/main_test.go go-backend/static/styles.css docs/design-system/*.html
git commit -m "feat: document spacing primitives"
```

## Task 4: Final Verification

**Files:**
- Modify: `go-backend/internal/web/ui/space.templ`
- Modify: `go-backend/internal/web/ui/variants.go`
- Modify: `go-backend/internal/web/ui/catalog/examples.go`
- Modify: `go-backend/static/styles.css`

- [ ] **Step 1: Run the full relevant test suite**

Run: `cd go-backend && go test ./internal/web/... ./cmd/designsystem -count=1`

Expected: PASS with:

- `xtablo-backend/internal/web/ui`
- `xtablo-backend/internal/web/ui/catalog`
- `xtablo-backend/cmd/designsystem`

all green.

- [ ] **Step 2: Inspect the final diff for spacing-only scope**

Run: `git diff --stat -- go-backend/internal/web/ui/space.templ go-backend/internal/web/ui/space_templ.go go-backend/internal/web/ui/variants.go go-backend/internal/web/ui/ui_test.go go-backend/internal/web/ui/catalog/examples.go go-backend/internal/web/ui/catalog/pages.go go-backend/internal/web/ui/catalog/catalog_test.go go-backend/cmd/designsystem/main_test.go go-backend/static/styles.css docs/design-system`

Expected: only spacing primitive, catalog, generated docs, and test files are included.

- [ ] **Step 3: Create the final verification commit if any follow-up polish was needed**

```bash
git add go-backend/internal/web/ui/space.templ go-backend/internal/web/ui/space_templ.go go-backend/internal/web/ui/variants.go go-backend/internal/web/ui/ui_test.go go-backend/internal/web/ui/catalog/examples.go go-backend/internal/web/ui/catalog/pages.go go-backend/internal/web/ui/catalog/catalog_test.go go-backend/cmd/designsystem/main_test.go go-backend/static/styles.css docs/design-system/*.html
git commit -m "chore: verify spacing primitives release"
```

## Self-Review

**Spec coverage check**

- `SpaceX` and `SpaceY`: covered in Task 2
- `SpacingStep` scale and `md` fallback: covered in Task 2 plus Task 1 tests
- stylesheet classes: covered in Task 2
- catalog page and examples: covered in Task 3
- design-system generated output: covered in Task 3 and Task 4
- no app call-site migrations: respected by file scope

**Placeholder scan**

- No `TODO`, `TBD`, or “appropriate handling” placeholders remain.
- All commands are explicit.
- All code-changing steps include concrete snippets.

**Type consistency**

- Shared type names are consistent throughout: `SpacingStep`, `SpaceProps`, `SpaceX`, `SpaceY`
- Class names are consistent throughout: `ui-space-x`, `ui-space-y`, `ui-space-x-{step}`, `ui-space-y-{step}`
