# Go Backend CSS Sources Per Primitive Implementation Plan

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

**Goal:** Refactor the Go backend design-system CSS so each primitive owns its own co-located source stylesheet while the app and generated catalog still ship a single `go-backend/static/styles.css`.

**Architecture:** Keep `go-backend/static/styles.css` as the only runtime stylesheet, but move CSS source ownership next to the UI code in `internal/web/ui` and `internal/web/ui/catalog`. Extend `cmd/buildstyles` so it concatenates an explicit ordered list of co-located CSS source files into the existing shipped artifact.

**Tech Stack:** Go, static CSS, `just`, Go `testing`, templ-generated HTML consumers

---

## Chunk 1: Build Contract

## Task 1: Tighten The Generator Contract Around Co-Located Sources

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

- [ ] **Step 1: Write the failing generator test against explicit source paths**

Update `go-backend/cmd/buildstyles/main_test.go` so the test uses co-located paths instead of a single css directory.

```go
func TestGenerateStylesConcatenatesSourcesInOrder(t *testing.T) {
	root := t.TempDir()
	basePath := filepath.Join(root, "internal", "web", "ui", "base.css")
	buttonPath := filepath.Join(root, "internal", "web", "ui", "button.css")

	for path, body := range map[string]string{
		basePath:   "/* base */\n.base { color: red; }\n",
		buttonPath: "/* button */\n.ui-button { color: blue; }\n",
	} {
		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
			t.Fatalf("mkdir for %s: %v", path, err)
		}
		if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
			t.Fatalf("write %s: %v", path, err)
		}
	}

	outputPath := filepath.Join(root, "static", "styles.css")
	if err := generateStyles(outputPath, []sourceFile{
		{Label: "base.css", Path: basePath},
		{Label: "button.css", Path: buttonPath},
	}); err != nil {
		t.Fatalf("generate styles: %v", err)
	}

	content, err := os.ReadFile(outputPath)
	if err != nil {
		t.Fatalf("read output: %v", err)
	}

	got := string(content)
	for _, want := range []string{
		"/* Source: base.css */",
		".base { color: red; }",
		"/* Source: button.css */",
		".ui-button { color: blue; }",
	} {
		if !strings.Contains(got, want) {
			t.Fatalf("expected %q in %q", want, got)
		}
	}
	if strings.Index(got, "/* Source: base.css */") > strings.Index(got, "/* Source: button.css */") {
		t.Fatalf("expected base.css before button.css, got %q", got)
	}
}
```

- [ ] **Step 2: Keep the built stylesheet assertions focused on the runtime contract**

Keep `TestSharedSemanticClassesExistInStylesheet` in `go-backend/internal/web/ui/ui_test.go` asserting the generated header and representative selectors:

```go
for _, want := range []string{
	`Code generated by cmd/buildstyles`,
	`.ui-button-solid.ui-button-default`,
	`.ui-badge-warning`,
	`.ui-icon-button-solid.ui-icon-button-neutral`,
	`.ui-card`,
	`.ui-space-x-md`,
	`.catalog-page`,
} {
	if !strings.Contains(css, want) {
		t.Fatalf("expected stylesheet to contain %q", want)
	}
}
```

- [ ] **Step 3: Run the focused tests to verify the generator test fails first**

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

Expected: FAIL in `TestGenerateStylesConcatenatesSourcesInOrder` until `cmd/buildstyles` supports explicit co-located source paths.

- [ ] **Step 4: Commit the updated contract tests**

```bash
git add go-backend/cmd/buildstyles/main_test.go go-backend/internal/web/ui/ui_test.go
git commit -m "test: define co-located css build contract"
```

## Chunk 2: Generator

## Task 2: Update The Stylesheet Generator For Co-Located Inputs

**Files:**
- Modify: `go-backend/cmd/buildstyles/main.go`
- Modify: `go-backend/justfile`

- [ ] **Step 1: Replace the single-directory model with explicit source file metadata**

Update `go-backend/cmd/buildstyles/main.go` to define each source file by label and path.

```go
type sourceFile struct {
	Label string
	Path  string
}

var sourceFiles = []sourceFile{
	{Label: "base.css", Path: filepath.Join("internal", "web", "ui", "base.css")},
	{Label: "catalog.css", Path: filepath.Join("internal", "web", "ui", "catalog", "catalog.css")},
	{Label: "button.css", Path: filepath.Join("internal", "web", "ui", "button.css")},
	{Label: "badge.css", Path: filepath.Join("internal", "web", "ui", "badge.css")},
	{Label: "icon-button.css", Path: filepath.Join("internal", "web", "ui", "icon-button.css")},
	{Label: "input.css", Path: filepath.Join("internal", "web", "ui", "input.css")},
	{Label: "textarea.css", Path: filepath.Join("internal", "web", "ui", "textarea.css")},
	{Label: "form-field.css", Path: filepath.Join("internal", "web", "ui", "form-field.css")},
	{Label: "modal.css", Path: filepath.Join("internal", "web", "ui", "modal.css")},
	{Label: "table.css", Path: filepath.Join("internal", "web", "ui", "table.css")},
	{Label: "empty-state.css", Path: filepath.Join("internal", "web", "ui", "empty-state.css")},
	{Label: "card.css", Path: filepath.Join("internal", "web", "ui", "card.css")},
	{Label: "spacing.css", Path: filepath.Join("internal", "web", "ui", "spacing.css")},
	{Label: "app.css", Path: filepath.Join("internal", "web", "ui", "app.css")},
}
```

- [ ] **Step 2: Adjust `generateStyles` to read from `[]sourceFile`**

Implement the signature and loop like this:

```go
func generateStyles(outputPath string, sources []sourceFile) error {
	var buf bytes.Buffer
	buf.WriteString("/* Code generated by cmd/buildstyles; DO NOT EDIT. */\n\n")

	for i, source := range sources {
		body, err := os.ReadFile(source.Path)
		if err != nil {
			return fmt.Errorf("read %s: %w", source.Path, err)
		}

		buf.WriteString("/* Source: ")
		buf.WriteString(source.Label)
		buf.WriteString(" */\n")
		buf.Write(bytes.TrimSpace(body))
		buf.WriteByte('\n')
		if i < len(sources)-1 {
			buf.WriteByte('\n')
		}
	}

	if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
		return fmt.Errorf("mkdir output dir: %w", err)
	}
	return os.WriteFile(outputPath, buf.Bytes(), 0o644)
}
```

- [ ] **Step 3: Simplify `main()` to keep only the output flag**

Use:

```go
func main() {
	output := flag.String("out", filepath.Join("static", "styles.css"), "output stylesheet path")
	flag.Parse()

	if err := generateStyles(*output, sourceFiles); err != nil {
		fmt.Fprintf(os.Stderr, "build styles: %v\n", err)
		os.Exit(1)
	}
}
```

- [ ] **Step 4: Keep `just` building the stylesheet before consumers run**

Ensure `go-backend/justfile` still calls `just build-styles` before `generate`, `test`, `build`, `dev`, and `run`. No change to the generated artifact path.

- [ ] **Step 5: Run the generator-focused tests**

Run: `cd go-backend && go test ./cmd/buildstyles -count=1`

Expected: PASS.

- [ ] **Step 6: Commit the generator changes**

```bash
git add go-backend/cmd/buildstyles/main.go go-backend/cmd/buildstyles/main_test.go go-backend/justfile
git commit -m "feat: build styles from co-located sources"
```

## Chunk 3: Co-Locate CSS Sources

## Task 3: Move Primitive And Catalog CSS Beside Their UI Code

**Files:**
- Create: `go-backend/internal/web/ui/base.css`
- Create: `go-backend/internal/web/ui/button.css`
- Create: `go-backend/internal/web/ui/badge.css`
- Create: `go-backend/internal/web/ui/icon-button.css`
- Create: `go-backend/internal/web/ui/input.css`
- Create: `go-backend/internal/web/ui/textarea.css`
- Create: `go-backend/internal/web/ui/form-field.css`
- Create: `go-backend/internal/web/ui/modal.css`
- Create: `go-backend/internal/web/ui/table.css`
- Create: `go-backend/internal/web/ui/empty-state.css`
- Create: `go-backend/internal/web/ui/card.css`
- Create: `go-backend/internal/web/ui/spacing.css`
- Create: `go-backend/internal/web/ui/app.css`
- Create: `go-backend/internal/web/ui/catalog/catalog.css`
- Delete: `go-backend/static/css/base.css`
- Delete: `go-backend/static/css/button.css`
- Delete: `go-backend/static/css/badge.css`
- Delete: `go-backend/static/css/icon-button.css`
- Delete: `go-backend/static/css/input.css`
- Delete: `go-backend/static/css/textarea.css`
- Delete: `go-backend/static/css/form-field.css`
- Delete: `go-backend/static/css/modal.css`
- Delete: `go-backend/static/css/table.css`
- Delete: `go-backend/static/css/empty-state.css`
- Delete: `go-backend/static/css/card.css`
- Delete: `go-backend/static/css/spacing.css`
- Delete: `go-backend/static/css/app.css`
- Delete: `go-backend/static/css/catalog.css`
- Modify: `go-backend/static/styles.css`

- [ ] **Step 1: Move the base rules into `internal/web/ui/base.css`**

This file owns:
- root tokens
- global reset rules
- `html`, `body`, `a`, `button`, `input`
- `.light-only`, `.dark-only`, `.visually-hidden`

- [ ] **Step 2: Move primitive selectors into matching co-located files without renaming selectors**

Keep each file limited to the selectors owned by that primitive:
- `button.css` owns `.ui-button*`
- `badge.css` owns `.ui-badge*`
- `icon-button.css` owns `.ui-icon-button*` and `.borderless-icon-button*`
- `input.css` owns `.ui-input*`
- `textarea.css` owns `.ui-textarea*`
- `form-field.css` owns `.ui-form-*`
- `modal.css` owns `.ui-modal*`
- `table.css` owns `.ui-table*`
- `empty-state.css` owns `.ui-empty-state*`
- `card.css` owns `.ui-card*`
- `spacing.css` owns `.ui-space-*`

- [ ] **Step 3: Move catalog-only selectors into `internal/web/ui/catalog/catalog.css`**

This file owns:
- `.catalog-page*`
- `.catalog-nav*`
- `.catalog-example*`
- `.catalog-inline`
- `.catalog-spacing-row`
- `.catalog-spacing-column`
- `.catalog-page-link*`

- [ ] **Step 4: Move remaining UI-layer app selectors into `internal/web/ui/app.css`**

This file owns non-primitive selectors that still belong to the UI layer, including:
- auth and home page classes such as `.login-screen`, `.auth-card-shell`, `.home-card`
- dashboard and overview classes such as `.dashboard-shell`, `.sidebar-*`, `.overview-*`, `.project-*`, `.tasks-*`
- responsive media queries and animation keyframes that support those selectors

- [ ] **Step 5: Delete the old `go-backend/static/css/` source files once the new copies exist**

Do not leave duplicated CSS sources behind.

- [ ] **Step 6: Rebuild the shipped stylesheet**

Run: `cd go-backend && go run ./cmd/buildstyles`

Expected: `go-backend/static/styles.css` contains the generated header and `/* Source: ... */` sections for the co-located files.

- [ ] **Step 7: Commit the co-located CSS move**

```bash
git add go-backend/internal/web/ui/*.css go-backend/internal/web/ui/catalog/catalog.css go-backend/static/styles.css go-backend/static/css
git commit -m "refactor: co-locate ui css sources"
```

## Chunk 4: Verification

## Task 4: Verify The Runtime Stylesheet And Consumers

**Files:**
- Verify: `go-backend/static/styles.css`
- Verify: `go-backend/internal/web/ui/ui_test.go`
- Verify: `go-backend/internal/web/ui/catalog/catalog_test.go`
- Verify: `go-backend/cmd/designsystem/main_test.go`

- [ ] **Step 1: Run the focused UI and catalog tests**

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

Expected: PASS.

- [ ] **Step 2: Run the broader web package verification**

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

Expected: PASS.

- [ ] **Step 3: Smoke-check the generated stylesheet headers**

Run: `cd go-backend && rg -n "Code generated by cmd/buildstyles|Source: button.css|Source: catalog.css|Source: app.css" static/styles.css`

Expected: matching lines for the generated header and representative source sections.

- [ ] **Step 4: Commit the final verification-safe state if anything changed during verification**

```bash
git add go-backend/static/styles.css
git commit -m "chore: refresh generated stylesheet"
```
