# Go Backend Tablo Edit And Color 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:** Add Go backend `/tablos` edit support with an edit icon beside delete, persisted hex colors, create validation for color, and an edit modal that includes both a hex input and a synced native color picker.

**Architecture:** Extend the existing `tablos` vertical slice in place. Persist `color` through the schema, sqlc, repository, handlers, and view models; add a small modal-state expansion so HTMX can render either the create modal or a per-tablo edit modal; keep status behavior untouched and validate `color` server-side as a canonical `#RRGGBB` string.

**Tech Stack:** Go, chi, templ, HTMX, PostgreSQL, pgx, sqlc, Go `net/http` tests, Tailwind-generated static CSS

---

## File Map

**Modify**

- `go-backend/router.go`
  - Register the edit-open and update routes for `/tablos/{tabloID}`.
- `go-backend/internal/db/schema.sql`
  - Add the new `color` column for persisted tablos.
- `go-backend/internal/db/queries.sql`
  - Extend create/list queries with `color` and add an update query.
- `go-backend/internal/db/repository.go`
  - Pass `color` through sqlc calls and map it back into the domain record.
- `go-backend/internal/db/sqlc/models.go`
  - Generated sqlc model updates for the new column.
- `go-backend/internal/db/sqlc/querier.go`
  - Generated sqlc interface updates for the new query.
- `go-backend/internal/db/sqlc/queries.sql.go`
  - Generated sqlc query bindings for create/list/update.
- `go-backend/internal/tablos/model.go`
  - Add `Color` to the record and create/update inputs.
- `go-backend/internal/web/handlers/auth.go`
  - Extend the repository interface with the update method if it is declared here.
- `go-backend/internal/web/handlers/tablos.go`
  - Add hex validation, modal state handling, edit-open handler, update handler, and in-memory repository support.
- `go-backend/internal/web/handlers/tablos_test.go`
  - Add repository and handler coverage for color persistence, modal rendering, and update behavior.
- `go-backend/internal/web/views/tablos_view.go`
  - Expand page and card view models with color, modal kind, edit URLs, and form values.
- `go-backend/internal/web/views/tablos.templ`
  - Add edit buttons, render color-driven styles, add create color field, and add the edit modal with hex input plus native color picker.
- `go-backend/internal/web/views/tablos_templ.go`
  - Generated templ output after updating `tablos.templ`.

**Commands / Generated Artifacts**

- `cd go-backend && just generate`
  - Regenerates:
    - `internal/db/sqlc/*.go`
    - `internal/web/views/*_templ.go`
- `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`
- `cd go-backend && go test ./...`

## Chunk 1: Lock Down Tests And Domain Inputs

### Task 1: Add failing tests for color-aware create and edit behavior

**Files:**
- Modify: `go-backend/internal/web/handlers/tablos_test.go`
- Modify: `go-backend/internal/tablos/model.go`

- [ ] **Step 1: Add failing in-memory repository assertions for `Color`**

Add tests or extend existing ones so repository fixtures assert that `CreateTabloInput` now accepts `Color` and that the returned/listed `TabloRecord` includes it.

```go
created, err := repo.CreateTablo(context.Background(), CreateTabloInput{
	OwnerID: user.ID,
	Name:    "Roadmap",
	Color:   "#3B82F6",
	Status:  TabloStatusTodo,
})
if err != nil {
	t.Fatalf("create tablo: %v", err)
}
if created.Color != "#3B82F6" {
	t.Fatalf("expected color to persist, got %q", created.Color)
}
```

- [ ] **Step 2: Add failing handler tests for create validation and edit modal rendering**

Add tests covering:

- create rejects missing color
- create rejects invalid color such as `#0af`
- edit modal renders current `name`
- edit modal renders current hex `color`
- edit modal renders a native color picker input with the same value
- update rejects another user's tablo

```go
if !strings.Contains(body, `type="color"`) {
	t.Fatalf("expected edit modal to render a color picker, got %q", body)
}
if !strings.Contains(body, `value="#3B82F6"`) {
	t.Fatalf("expected edit modal to use the current color, got %q", body)
}
```

- [ ] **Step 3: Run handler tests to verify they fail**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`

Expected: FAIL because `Color`, edit handlers, update behavior, and modal markup do not exist yet.

- [ ] **Step 4: Add domain input fields needed by the tests**

Update `go-backend/internal/tablos/model.go` so the types can express the feature:

```go
type Record struct {
	ID        uuid.UUID
	OwnerID   uuid.UUID
	Name      string
	Color     string
	Status    Status
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time
}

type CreateInput struct {
	OwnerID uuid.UUID
	Name    string
	Color   string
	Status  Status
}

type UpdateInput struct {
	ID      uuid.UUID
	OwnerID uuid.UUID
	Name    string
	Color   string
}
```

- [ ] **Step 5: Re-run handler tests to confirm the remaining failures are implementation failures, not compile-shape failures**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`

Expected: FAIL, but now due to missing repository/handler/view behavior rather than missing domain fields.

- [ ] **Step 6: Commit the test-and-domain scaffold**

```bash
git add go-backend/internal/tablos/model.go go-backend/internal/web/handlers/tablos_test.go
git commit -m "test: define tablo color and edit behavior"
```

## Chunk 2: Persist Color And Update Repository Paths

### Task 2: Add `color` to schema, sqlc, and repository behavior

**Files:**
- Modify: `go-backend/internal/db/schema.sql`
- Modify: `go-backend/internal/db/queries.sql`
- Modify: `go-backend/internal/db/repository.go`
- Modify: `go-backend/internal/db/sqlc/models.go`
- Modify: `go-backend/internal/db/sqlc/querier.go`
- Modify: `go-backend/internal/db/sqlc/queries.sql.go`
- Modify: `go-backend/internal/web/handlers/auth.go`
- Modify: `go-backend/internal/web/handlers/tablos.go`

- [ ] **Step 1: Add the database column to the schema**

Update the `public.tablos` table definition:

```sql
CREATE TABLE IF NOT EXISTS public.tablos (
  id uuid PRIMARY KEY,
  owner_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
  name text NOT NULL,
  color text NOT NULL,
  status text NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  deleted_at timestamptz NULL
);
```

- [ ] **Step 2: Extend sqlc queries and add an update statement**

Update `go-backend/internal/db/queries.sql`:

```sql
-- name: CreateTablo :one
INSERT INTO public.tablos (
  id, owner_id, name, color, status, created_at, updated_at
) VALUES (
  $1, $2, $3, $4, $5, now(), now()
)
RETURNING id, owner_id, name, color, status, created_at, updated_at, deleted_at;

-- name: ListTablos :many
SELECT id, owner_id, name, color, status, created_at, updated_at, deleted_at
FROM public.tablos
...

-- name: UpdateTablo :execrows
UPDATE public.tablos
SET name = $3, color = $4, updated_at = now()
WHERE id = $1
  AND owner_id = $2
  AND deleted_at IS NULL;
```

- [ ] **Step 3: Regenerate generated files**

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

Expected: PASS with regenerated `internal/db/sqlc/*.go` and `internal/web/views/tablos_templ.go`.

- [ ] **Step 4: Thread `color` through repository and repository interface code**

Update:

- `go-backend/internal/db/repository.go`
- `go-backend/internal/web/handlers/auth.go`
- `go-backend/internal/web/handlers/tablos.go`

Representative implementation:

```go
row, err := r.queries.CreateTablo(ctx, sqlcdb.CreateTabloParams{
	ID:      uuid.New(),
	OwnerID: input.OwnerID,
	Name:    strings.TrimSpace(input.Name),
	Color:   input.Color,
	Status:  string(input.Status),
})
```

and:

```go
func (r *PostgresAuthRepository) UpdateTablo(ctx context.Context, input tablomodel.UpdateInput) error {
	rows, err := r.queries.UpdateTablo(ctx, sqlcdb.UpdateTabloParams{
		ID:      input.ID,
		OwnerID: input.OwnerID,
		Name:    strings.TrimSpace(input.Name),
		Color:   input.Color,
	})
	if err != nil {
		return err
	}
	if rows == 0 {
		return tablomodel.ErrNotFound
	}
	return nil
}
```

- [ ] **Step 5: Update the in-memory repository implementation**

Add `Color` storage in `CreateTablo` and implement `UpdateTablo` in the in-memory repository so handler tests can pass without Postgres.

```go
tablo.Color = input.Color
...
existing.Name = strings.TrimSpace(input.Name)
existing.Color = input.Color
existing.UpdatedAt = time.Now().UTC()
```

- [ ] **Step 6: Run handler tests to verify repository-backed failures are cleared**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`

Expected: FAIL only on handler/view/modal behavior that still needs implementation.

- [ ] **Step 7: Commit persistence changes**

```bash
git add go-backend/internal/db/schema.sql go-backend/internal/db/queries.sql go-backend/internal/db/repository.go go-backend/internal/db/sqlc go-backend/internal/web/handlers/auth.go go-backend/internal/web/handlers/tablos.go
git commit -m "feat: persist go-backend tablo colors"
```

## Chunk 3: Add Edit Handlers, Validation, And Modal State

### Task 3: Implement hex validation and edit/update request handling

**Files:**
- Modify: `go-backend/router.go`
- Modify: `go-backend/internal/web/handlers/auth.go`
- Modify: `go-backend/internal/web/handlers/tablos.go`
- Modify: `go-backend/internal/web/views/tablos_view.go`

- [ ] **Step 1: Register the new routes**

Update `go-backend/router.go`:

```go
mux.Get("/tablos/{tabloID}/edit", authHandler.GetEditTabloModal())
mux.Post("/tablos/{tabloID}", authHandler.PostTabloUpdate())
```

- [ ] **Step 2: Add validation helpers in `tablos.go`**

Add a strict hex validator and normalize accepted values:

```go
var tabloColorPattern = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)

func normalizeTabloColor(raw string) (string, bool) {
	color := strings.TrimSpace(raw)
	if !tabloColorPattern.MatchString(color) {
		return "", false
	}
	return strings.ToUpper(color), true
}
```

- [ ] **Step 3: Expand modal state beyond `ModalOpen bool`**

Refactor page state and view model so the page can represent:

- no modal
- create modal
- edit modal

Suggested shape:

```go
type TabloModalKind string

const (
	TabloModalNone   TabloModalKind = ""
	TabloModalCreate TabloModalKind = "create"
	TabloModalEdit   TabloModalKind = "edit"
)
```

Carry through:

- active modal kind
- form name
- form color
- editing tablo ID
- error message

- [ ] **Step 4: Update create handling to require `color`**

In `PostTablos()`:

- parse `name` and `color`
- reject empty/invalid `color` with `422`
- keep the create modal open on validation failure
- pass normalized hex color into `CreateTabloInput`

- [ ] **Step 5: Implement edit-open and update handlers**

Add:

- `GetEditTabloModal() http.HandlerFunc`
- `PostTabloUpdate() http.HandlerFunc`

Behavior:

- parse `tabloID`
- find the owner-visible tablo from `ListTablos(...)` or add a narrow lookup helper if needed
- prefill modal values from the existing record
- on update, validate `name` and `color`
- call `UpdateTablo(...)`
- re-render `/tablos` with the current `view`, `q`, and `status`

- [ ] **Step 6: Expose edit URLs and modal helpers from the view model**

Update `go-backend/internal/web/views/tablos_view.go` so it can generate:

- `CreateModalHref()`
- `EditModalHref(tabloID string)`
- `CloseModalHref()`
- card/list-level edit request URLs preserving current query state

- [ ] **Step 7: Run handler tests to verify only templ rendering remains**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`

Expected: FAIL only where rendered markup still lacks the new buttons/fields or where templ generation has not been refreshed yet.

- [ ] **Step 8: Commit handler and view-model logic**

```bash
git add go-backend/router.go go-backend/internal/web/handlers/tablos.go go-backend/internal/web/views/tablos_view.go
git commit -m "feat: add go-backend tablo edit handlers"
```

## Chunk 4: Render Edit UI, Color Picker, And Final Verification

### Task 4: Update templ views for edit action, color input, and color picker

**Files:**
- Modify: `go-backend/internal/web/views/tablos.templ`
- Modify: `go-backend/internal/web/views/tablos_templ.go`
- Modify: `go-backend/internal/web/handlers/tablos_test.go`

- [ ] **Step 1: Add edit icon buttons before delete in grid and list views**

In `go-backend/internal/web/views/tablos.templ`, add an edit button component immediately before the delete button in:

- `TabloGridCardWithAttrs`
- `TabloListRow`

Representative shape:

```templ
@ui.IconButton(ui.IconButtonProps{
	Label:   "Modifier le projet",
	Icon:    "pencil",
	Variant: ui.IconButtonVariantGhost,
	Type:    "button",
	Attrs: templ.Attributes{
		"hx-get":      tablo.EditRequestURL,
		"hx-target":   "#app-main-content",
		"hx-swap":     "outerHTML",
		"hx-push-url": "true",
	},
})
```

- [ ] **Step 2: Render stored colors in card/list accents**

Replace or bypass accent-token-only rendering where needed so the card uses the persisted hex color directly.

Representative shape:

```templ
<div class="project-avatar" style={ "background-color: " + tablo.Color }>
```

and:

```templ
<div class="project-progress-bar" style={ "width: " + tablo.ProgressLabel + "; background-color: " + tablo.Color }>
```

Keep this narrow; do not refactor unrelated styles.

- [ ] **Step 3: Extend the create modal with a hex color input**

Add a `Couleur` field to `CreateTabloModalBody` using the shared form primitives, with placeholder/example text like `#3B82F6`.

- [ ] **Step 4: Add the edit modal with both controls**

Add an edit modal body that renders:

- `Nom du projet`
- a hex text input
- a native color picker input

Use the same `vm.FormColor` source for both values.

Representative shape:

```templ
@ui.FormField(ui.FormFieldProps{
	Label: "Couleur",
	For:   "tablo-color",
	Field: templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
		return templ.Join(
			ui.Input(ui.InputProps{
				ID:          "tablo-color",
				Name:        "color",
				Value:       vm.FormColor,
				Placeholder: "#3B82F6",
				Type:        "text",
			}),
			templ.Raw(`<input type="color" name="color_picker" value="` + vm.FormColor + `" class="..." oninput="this.form.color.value=this.value">`),
		).Render(ctx, w)
	}),
})
```

If inline JS is undesirable inside templ, use the smallest equivalent attribute approach already accepted by the codebase. Keep the submission source canonical: the posted `color` field must still carry the validated hex value.

- [ ] **Step 5: Switch modal rendering by modal kind and regenerate templ**

Update `TablosPageContent` so it renders:

- create modal when modal kind is `create`
- edit modal when modal kind is `edit`

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

Expected: PASS with regenerated `go-backend/internal/web/views/tablos_templ.go`.

- [ ] **Step 6: Run the focused handler suite**

Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`

Expected: PASS.

- [ ] **Step 7: Run the full Go backend suite**

Run: `cd go-backend && go test ./...`

Expected: PASS.

- [ ] **Step 8: Commit the UI and final verification**

```bash
git add go-backend/internal/web/views/tablos.templ go-backend/internal/web/views/tablos_templ.go go-backend/internal/web/handlers/tablos_test.go
git commit -m "feat: add go-backend tablo edit modal"
```

## Final Verification Checklist

- [ ] `cd go-backend && just generate`
- [ ] `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`
- [ ] `cd go-backend && go test ./...`
- [ ] Manually verify in the browser via `cd go-backend && just dev`:
  - create modal requires a valid hex color
  - grid cards show edit to the left of delete
  - list rows show edit to the left of delete
  - edit modal preloads current name and color
  - edit modal shows both hex input and color picker
  - changing the picker updates the submitted hex value
  - successful save refreshes `/tablos` and closes the modal

Plan complete and saved to `docs/superpowers/plans/2026-05-10-go-backend-tablo-edit-color.md`. Ready to execute?
