Update go-backend tasks docs for assignee support

This commit is contained in:
Arthur Belleville 2026-05-10 18:37:44 +02:00
parent ef7ccd8c6f
commit dd6e5b7d64
No known key found for this signature in database
2 changed files with 715 additions and 4 deletions

View file

@ -0,0 +1,701 @@
# Go Backend Tasks And Etapes 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:** Build the first real `/tasks` vertical slice in `go-backend` with SQL-backed owner-only CRUD for tasks and etapes, optional `assignee_id`, runtime "Sans etape" grouping, and HTMX-driven server-rendered page flows.
**Architecture:** Extend the existing Go app by adding a small `tasks` domain, a self-referential `public.tasks` table with optional `assignee_id`, sqlc-backed repository methods, owner-scoped handlers, and `templ` views for grouped task rendering. Keep all interactions server-rendered, with HTMX only refreshing the `/tasks` content or modal fragments, and keep "Sans etape" as a pure view concern derived from regular tasks with `parent_task_id = null`.
**Tech Stack:** Go, chi, templ, HTMX, PostgreSQL, pgx, sqlc, Go standard `net/http` testing
---
## File Structure
**Existing files to modify**
- `go-backend/internal/db/schema.sql`
- Add the `public.tasks` table, optional `assignee_id`, indexes, `CHECK` constraints, and trigger/function for parent validation.
- `go-backend/internal/db/queries.sql`
- Add sqlc queries for owner-scoped create, list, fetch, update, assignee persistence, child-clearing, and soft delete.
- `go-backend/internal/db/repository.go`
- Add Postgres-backed task methods and the transaction for deleting an etape and clearing child `parent_task_id`.
- `go-backend/internal/web/handlers/auth.go`
- Extend the repository interface with task methods and delegate `GetTasksPage()` to real task rendering.
- `go-backend/internal/web/handlers/in_memory_auth_repository.go`
- Add in-memory task storage and behavior for tests.
- `go-backend/internal/web/views/home.go`
- Remove or stop depending on hard-coded task placeholder data if `/tasks` and overview rendering share helpers.
- `go-backend/router.go`
- Register task mutation and fragment routes.
- `go-backend/router_test.go`
- Add full-router coverage for `/tasks` page and mutation flows where end-to-end routing matters.
**New files to create**
- `go-backend/internal/tasks/model.go`
- Task and etape domain types, status constants, validation helpers, and list/update inputs, including optional assignee support.
- `go-backend/internal/web/handlers/tasks.go`
- Query-state parsing, form parsing, owner checks, list rendering, and mutation handlers.
- `go-backend/internal/web/handlers/tasks_test.go`
- Focused handler and in-memory repository tests for tasks and etapes.
- `go-backend/internal/web/views/tasks.templ`
- `/tasks` page content, grouped sections, task rows, etape sections, assignee display, and modal/form fragments.
- `go-backend/internal/web/views/tasks_view.go`
- View models and grouping helpers, including runtime "Sans etape" construction.
**Generated files expected to change**
- `go-backend/internal/db/sqlc/*.go`
- Regenerated by `just generate` after schema/query updates.
- `go-backend/internal/web/views/*_templ.go`
- Regenerated by `just generate` after `templ` changes.
**Test and verification commands**
- `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
- `cd go-backend && go test ./...`
- `cd go-backend && just generate`
- `cd go-backend && just build`
## Chunk 1: Task Domain And In-Memory Contract
### Task 1: Add failing in-memory repository tests for task and etape behavior
**Files:**
- Create: `go-backend/internal/web/handlers/tasks_test.go`
- Modify: `go-backend/internal/web/handlers/in_memory_auth_repository.go`
- [ ] **Step 1: Write the failing tests for create/list/delete task behavior**
Add focused tests that define the repository contract before production code exists:
```go
func TestInMemoryTasksListExcludesSoftDeletedRows(t *testing.T) {
repo := NewInMemoryAuthRepository()
user, _ := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
etape, err := repo.CreateTask(context.Background(), CreateTaskInput{
OwnerID: user.ID,
TabloID: mustCreateOwnedTablo(t, repo, user.ID).ID,
Title: "Etape 1",
IsEtape: true,
Status: tasks.StatusTodo,
})
if err != nil {
t.Fatal(err)
}
task, err := repo.CreateTask(context.Background(), CreateTaskInput{
OwnerID: user.ID,
TabloID: etape.TabloID,
Title: "Task 1",
Status: tasks.StatusTodo,
ParentTaskID: &etape.ID,
})
if err != nil {
t.Fatal(err)
}
if err := repo.SoftDeleteTask(context.Background(), task.ID, user.ID); err != nil {
t.Fatal(err)
}
records, err := repo.ListTasksByTablo(context.Background(), ListTasksByTabloInput{
OwnerID: user.ID,
TabloID: etape.TabloID,
})
if err != nil {
t.Fatal(err)
}
if len(records) != 1 || records[0].ID != etape.ID {
t.Fatalf("expected only etape to remain visible, got %#v", records)
}
}
```
- [ ] **Step 2: Write failing tests for etape-specific invariants**
Add tests for:
- etape cannot have a parent
- parent must be an etape
- assignee persists when set and clears to null when unset
- deleting an etape clears active child `parent_task_id`
- owner scoping rejects another user
Example:
```go
func TestInMemoryDeleteEtapeClearsChildParentID(t *testing.T) {
repo := NewInMemoryAuthRepository()
user, _ := repo.GetAuthUserByEmail(context.Background(), "demo@xtablo.com")
tablo := mustCreateOwnedTablo(t, repo, user.ID)
etape := mustCreateEtape(t, repo, user.ID, tablo.ID, "Launch")
child := mustCreateTask(t, repo, user.ID, tablo.ID, etape.ID, "Ship copy")
if err := repo.SoftDeleteTask(context.Background(), etape.ID, user.ID); err != nil {
t.Fatal(err)
}
updated, err := repo.GetTaskByID(context.Background(), child.ID, user.ID)
if err != nil {
t.Fatal(err)
}
if updated.ParentTaskID != nil {
t.Fatalf("expected child task to move to Sans etape, got parent %v", *updated.ParentTaskID)
}
}
```
- [ ] **Step 3: Run the focused tests to verify they fail**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
Expected: FAIL with missing task types or missing repository methods.
- [ ] **Step 4: Add the minimal task domain types and in-memory storage**
Create `internal/tasks/model.go` with:
```go
package tasks
type Status string
const (
StatusTodo Status = "todo"
StatusInProgress Status = "in_progress"
StatusInReview Status = "in_review"
StatusDone Status = "done"
)
```
Add repository-facing types used by handlers:
- `TaskRecord`
- `CreateTaskInput`
- `UpdateTaskInput`
- `ListTasksByTabloInput`
- validation helpers such as `ParseStatus`
- optional `AssigneeID *uuid.UUID` on the task record and mutation inputs
Extend `InMemoryAuthRepository` with:
- `tasks map[uuid.UUID]TaskRecord`
- `CreateTask`
- `ListTasksByTablo`
- `GetTaskByID`
- `UpdateTask`
- `SoftDeleteTask`
- [ ] **Step 5: Re-run the focused tests to verify they pass**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
Expected: PASS for the new in-memory task and etape behavior tests.
- [ ] **Step 6: Commit**
```bash
git add go-backend/internal/tasks/model.go go-backend/internal/web/handlers/in_memory_auth_repository.go go-backend/internal/web/handlers/tasks_test.go
git commit -m "feat: add in-memory tasks and etapes support"
```
## Chunk 2: SQL Schema, sqlc, And Postgres Repository
### Task 2: Add the SQL schema for tasks and etapes
**Files:**
- Modify: `go-backend/internal/db/schema.sql`
- [ ] **Step 1: Write a failing repository-shaped test comment block or focused TODO test to lock the SQL contract**
If there is still no dedicated DB integration harness, add a small repository contract test stub in `tasks_test.go` that documents the needed shape:
```go
func TestTaskRepositoryContractDocumentsOwnerScopedCRUD(t *testing.T) {
t.Skip("Enable once Postgres-backed repository tests exist for go-backend tasks")
}
```
The point is to define the target before editing SQL.
- [ ] **Step 2: Run the focused tests to keep the failure visible**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
Expected: existing tests still pass, and the repository contract work remains unimplemented in Postgres.
- [ ] **Step 3: Add the `public.tasks` table, indexes, and constraints**
Update `schema.sql` with the minimal approved schema:
```sql
CREATE TABLE IF NOT EXISTS public.tasks (
id uuid PRIMARY KEY,
tablo_id uuid NOT NULL REFERENCES public.tablos(id) ON DELETE CASCADE,
owner_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
title text NOT NULL,
description text NOT NULL DEFAULT '',
status text NOT NULL CHECK (status IN ('todo', 'in_progress', 'in_review', 'done')),
assignee_id uuid NULL REFERENCES public.users(id) ON DELETE SET NULL,
is_etape boolean NOT NULL DEFAULT false,
parent_task_id uuid NULL REFERENCES public.tasks(id) ON DELETE SET NULL,
due_date date NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL,
CHECK (NOT is_etape OR parent_task_id IS NULL)
);
```
Add indexes for:
- `(owner_id, tablo_id, deleted_at)`
- `(assignee_id, deleted_at)`
- `(tablo_id, is_etape, deleted_at)`
- `(parent_task_id)`
- `(tablo_id, due_date)`
- [ ] **Step 4: Add the parent validation function and trigger**
Implement a trigger function in `schema.sql` that rejects:
- etape rows with a parent
- parent rows outside the same owner
- parent rows outside the same tablo
- parent rows that are not etapes
- parent rows that are soft-deleted
- [ ] **Step 5: Regenerate sqlc once the schema compiles**
Run: `cd go-backend && just generate`
Expected: PASS with updated generated database types.
- [ ] **Step 6: Commit**
```bash
git add go-backend/internal/db/schema.sql go-backend/internal/db/sqlc
git commit -m "feat: add tasks schema and integrity rules"
```
### Task 3: Add sqlc queries and Postgres repository methods
**Files:**
- Modify: `go-backend/internal/db/queries.sql`
- Modify: `go-backend/internal/db/repository.go`
- Modify: `go-backend/internal/web/handlers/auth.go`
- Generated: `go-backend/internal/db/sqlc/*`
- [ ] **Step 1: Add the failing task repository method signatures to the shared interface**
Extend the repository interface in `auth.go` with:
```go
CreateTask(ctx context.Context, input CreateTaskInput) (TaskRecord, error)
GetTaskByID(ctx context.Context, taskID uuid.UUID, ownerID uuid.UUID) (TaskRecord, error)
ListTasksByTablo(ctx context.Context, input ListTasksByTabloInput) ([]TaskRecord, error)
ListTasksByOwner(ctx context.Context, ownerID uuid.UUID) ([]TaskRecord, error)
UpdateTask(ctx context.Context, input UpdateTaskInput) (TaskRecord, error)
SoftDeleteTask(ctx context.Context, taskID uuid.UUID, ownerID uuid.UUID) error
```
- [ ] **Step 2: Run the focused tests to verify the Postgres implementation is still missing**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
Expected: FAIL or compile errors in the Postgres repository until real methods are added.
- [ ] **Step 3: Add sqlc queries for owner-scoped CRUD**
Add at minimum:
- `CreateTask`
- `GetTaskByID`
- `ListTasksByOwner`
- `ListTasksByTablo`
- `UpdateTask`
- `ClearTaskChildrenParent`
- `SoftDeleteTask`
Example update query shape:
```sql
-- name: UpdateTask :one
UPDATE public.tasks
SET
title = $3,
description = $4,
status = $5,
due_date = $6,
assignee_id = $7,
parent_task_id = $8,
updated_at = now()
WHERE id = $1
AND owner_id = $2
AND deleted_at IS NULL
RETURNING *;
```
- [ ] **Step 4: Regenerate sqlc**
Run: `cd go-backend && just generate`
Expected: PASS with generated query methods for the new statements.
- [ ] **Step 5: Implement the Postgres repository methods**
Add Go mapping code in `repository.go`:
- trim titles
- default empty description to `""`
- map nullable `due_date`, `deleted_at`, `assignee_id`, and `parent_task_id`
- wrap etape delete in a transaction:
- fetch current row
- if etape, clear active child parents
- soft-delete the target row
- [ ] **Step 6: Re-run the focused tests to verify compilation and behavior**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|InMemoryTasks'`
Expected: PASS with repository interface and implementations in sync.
- [ ] **Step 7: Commit**
```bash
git add go-backend/internal/db/queries.sql go-backend/internal/db/repository.go go-backend/internal/db/sqlc go-backend/internal/web/handlers/auth.go
git commit -m "feat: add sqlc-backed task repository methods"
```
## Chunk 3: Task Page Rendering And Grouping
### Task 4: Replace the placeholder `/tasks` page with real grouped task content
**Files:**
- Create: `go-backend/internal/web/views/tasks_view.go`
- Create: `go-backend/internal/web/views/tasks.templ`
- Modify: `go-backend/internal/web/handlers/auth.go`
- Modify: `go-backend/internal/web/views/home.go`
- [ ] **Step 1: Write a failing handler test for real `/tasks` page rendering**
Add a test that proves the page is no longer placeholder-only:
```go
func TestGetTasksPageRendersEtapesAndSansEtapeSections(t *testing.T) {
repo := NewInMemoryAuthRepository()
handler := NewAuthHandler(repo)
sessionCookie := loginTestUser(t, handler, "demo@xtablo.com", "xtablo-demo")
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
userID, _ := handler.currentUserID(req.Context(), req)
tablo := mustCreateOwnedTablo(t, repo, userID)
etape := mustCreateEtape(t, repo, userID, tablo.ID, "Production")
_ = mustCreateTask(t, repo, userID, tablo.ID, etape.ID, "Cut footage")
_ = mustCreateParentlessTask(t, repo, userID, tablo.ID, "Inbox task")
pageReq := httptest.NewRequest(http.MethodGet, "/tasks", nil)
pageReq.AddCookie(sessionCookie)
rec := httptest.NewRecorder()
handler.GetTasksPage().ServeHTTP(rec, pageReq)
body := rec.Body.String()
for _, want := range []string{"Taches", "Production", "Sans etape", "Inbox task"} {
if !strings.Contains(body, want) {
t.Fatalf("expected body to contain %q, got %q", want, body)
}
}
}
```
- [ ] **Step 2: Run the focused test to verify it fails**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|GetTasksPage'`
Expected: FAIL because `/tasks` still renders placeholder content.
- [ ] **Step 3: Build the task page view model and grouping helpers**
Implement `tasks_view.go` with helpers to:
- group rows by tablo
- split etapes from regular tasks
- attach child tasks under their etape
- synthesize a "Sans etape" section from parentless regular tasks
- surface assignee labels in row view models when present
- format due dates and labels for the view
- [ ] **Step 4: Add the real `templ` page content**
Implement `tasks.templ` components for:
- page shell
- tablo section
- etape section
- "Sans etape" section
- task row
- empty state
- [ ] **Step 5: Update `GetTasksPage()` to load owner data and render the new view**
Load:
- current owner
- owner tablos for the sidebar
- owner tasks for grouped rendering
Render the real task content for both full-page and HTMX requests.
- [ ] **Step 6: Re-run the focused tests to verify the page rendering passes**
Run: `cd go-backend && go test ./internal/web/handlers -run 'Tasks|GetTasksPage'`
Expected: PASS with grouped task content visible.
- [ ] **Step 7: Commit**
```bash
git add go-backend/internal/web/views/tasks_view.go go-backend/internal/web/views/tasks.templ go-backend/internal/web/handlers/auth.go go-backend/internal/web/views/home.go go-backend/internal/web/views/*_templ.go go-backend/internal/web/handlers/tasks_test.go
git commit -m "feat: render grouped tasks page"
```
## Chunk 4: Create And Edit Flows
### Task 5: Add create and edit handlers for tasks and etapes
**Files:**
- Create: `go-backend/internal/web/handlers/tasks.go`
- Modify: `go-backend/router.go`
- Modify: `go-backend/internal/web/handlers/tasks_test.go`
- Modify: `go-backend/internal/web/views/tasks.templ`
- [ ] **Step 1: Write failing handler tests for `POST /tasks` create flows**
Add tests for:
- creating a regular task
- creating an etape
- rejecting empty title
- rejecting a non-etape parent
- persisting `assignee_id` when provided
Example:
```go
func TestPostTasksCreatesEtape(t *testing.T) {
// Arrange authenticated owner, tablo, form post with is_etape=true
// Assert 200 and page contains the new etape title.
}
```
- [ ] **Step 2: Write a failing handler test for `GET /tasks/{taskID}/edit`**
Assert the returned fragment contains the current title, description, status, parent selector state, and assignee selector state.
- [ ] **Step 3: Run the focused tests to verify they fail**
Run: `cd go-backend && go test ./internal/web/handlers -run 'PostTasks|EditTask|Tasks'`
Expected: FAIL due to missing routes and handlers.
- [ ] **Step 4: Implement form parsing and owner-scoped create logic**
In `tasks.go`, parse:
- `tablo_id`
- `title`
- `description`
- `status`
- `due_date`
- `assignee_id`
- `parent_task_id`
- `is_etape`
On success, create the row through the repository and re-render `/tasks`.
- [ ] **Step 5: Implement the edit fragment handler**
Load the owner-scoped task and render a fragment with:
- title field
- description field
- status control
- due date input
- parent etape select for regular tasks only
- [ ] **Step 6: Re-run the focused tests to verify create and edit pass**
Run: `cd go-backend && go test ./internal/web/handlers -run 'PostTasks|EditTask|Tasks'`
Expected: PASS for create and edit-fragment coverage.
- [ ] **Step 7: Commit**
```bash
git add go-backend/internal/web/handlers/tasks.go go-backend/router.go go-backend/internal/web/handlers/tasks_test.go go-backend/internal/web/views/tasks.templ go-backend/internal/web/views/*_templ.go
git commit -m "feat: add task and etape create flows"
```
### Task 6: Add `PATCH /tasks/{taskID}` for updates
**Files:**
- Modify: `go-backend/internal/web/handlers/tasks.go`
- Modify: `go-backend/router.go`
- Modify: `go-backend/internal/web/handlers/tasks_test.go`
- [ ] **Step 1: Write failing handler tests for each editable field**
Add coverage for:
- updating title
- updating description
- updating status
- updating due date
- updating assignee
- updating parent etape
Example:
```go
func TestPatchTaskUpdatesParentEtape(t *testing.T) {
// Arrange a task with no parent and a valid etape in the same tablo.
// Submit PATCH /tasks/{id}.
// Assert the refreshed page or fragment shows the updated grouping.
}
```
- [ ] **Step 2: Run the focused tests to verify they fail**
Run: `cd go-backend && go test ./internal/web/handlers -run 'PatchTask|Tasks'`
Expected: FAIL because the PATCH route does not exist yet.
- [ ] **Step 3: Implement `PATCH /tasks/{taskID}`**
Add handler logic to:
- parse owner session
- parse and validate target id
- read form payload
- allow `assignee_id` to be set or cleared
- keep etapes parentless
- update the task through the repository
- return refreshed page content or task fragment
- [ ] **Step 4: Re-run the focused tests to verify the PATCH flow passes**
Run: `cd go-backend && go test ./internal/web/handlers -run 'PatchTask|Tasks'`
Expected: PASS for all editable-field update cases.
- [ ] **Step 5: Commit**
```bash
git add go-backend/internal/web/handlers/tasks.go go-backend/router.go go-backend/internal/web/handlers/tasks_test.go
git commit -m "feat: add task patch update flow"
```
## Chunk 5: Delete Flow, Full-Router Coverage, And Verification
### Task 7: Add owner-scoped delete behavior for tasks and etapes
**Files:**
- Modify: `go-backend/internal/web/handlers/tasks.go`
- Modify: `go-backend/internal/web/handlers/tasks_test.go`
- Modify: `go-backend/router.go`
- [ ] **Step 1: Write failing delete tests**
Add tests for:
- deleting a regular task
- deleting an etape
- child tasks moving into "Sans etape" after etape deletion
- rejecting delete from another owner
- [ ] **Step 2: Run the focused tests to verify they fail**
Run: `cd go-backend && go test ./internal/web/handlers -run 'DeleteTask|Tasks'`
Expected: FAIL because delete behavior is still missing or incomplete.
- [ ] **Step 3: Implement `DELETE /tasks/{taskID}`**
Call the repository `SoftDeleteTask` method and re-render the `/tasks` page. Ensure etape child-parent clearing remains transaction-backed in the repository rather than reproduced in handlers.
- [ ] **Step 4: Re-run the focused tests to verify they pass**
Run: `cd go-backend && go test ./internal/web/handlers -run 'DeleteTask|Tasks'`
Expected: PASS for delete flows.
- [ ] **Step 5: Commit**
```bash
git add go-backend/internal/web/handlers/tasks.go go-backend/internal/web/handlers/tasks_test.go go-backend/router.go
git commit -m "feat: add task and etape delete flow"
```
### Task 8: Add full-router coverage and run full verification
**Files:**
- Modify: `go-backend/router_test.go`
- Generated: `go-backend/internal/db/sqlc/*.go`
- Generated: `go-backend/internal/web/views/*_templ.go`
- [ ] **Step 1: Add end-to-end router tests for `/tasks`**
Cover:
- authenticated `GET /tasks`
- HTMX `GET /tasks`
- at least one create flow through the router
- at least one patch flow through the router
- at least one delete flow through the router
- [ ] **Step 2: Run the focused router tests**
Run: `cd go-backend && go test ./... -run 'TasksPage|TasksRouter|PatchTask|DeleteTask'`
Expected: PASS for router-level task coverage.
- [ ] **Step 3: Regenerate assets and generated code**
Run: `cd go-backend && just generate`
Expected: PASS with fresh `templ` and `sqlc` outputs.
- [ ] **Step 4: Format and run the full test suite**
Run: `cd go-backend && gofmt -w . && go test ./...`
Expected: PASS for the full Go suite.
- [ ] **Step 5: Run build verification**
Run: `cd go-backend && just build`
Expected: PASS with a successful Go build and CSS generation.
- [ ] **Step 6: Manual smoke check**
Run: `cd go-backend && just run`
Expected: the app starts, `/tasks` renders real grouped data, create/edit/delete interactions work for the demo owner, and assignee flows behave correctly.
- [ ] **Step 7: Commit**
```bash
git add go-backend/router_test.go go-backend/internal/db/sqlc go-backend/internal/web/views/*_templ.go
git commit -m "test: verify go-backend tasks and etapes flows"
```
---
Plan complete and saved to `docs/superpowers/plans/2026-05-10-go-backend-tasks-etapes.md`. Ready to execute?

View file

@ -4,7 +4,7 @@
**Goal** **Goal**
Build the first real tasks vertical slice in `go-backend`: owner-scoped storage, SQL-backed CRUD for both tasks and etapes, and a server-rendered `/tasks` page that can create, list, update, and delete them. Build the first real tasks vertical slice in `go-backend`: owner-scoped storage, SQL-backed CRUD for both tasks and etapes, optional assignees via `assignee_id`, and a server-rendered `/tasks` page that can create, list, update, and delete them.
**Scope** **Scope**
@ -19,6 +19,7 @@ Build the first real tasks vertical slice in `go-backend`: owner-scoped storage,
- description - description
- status - status
- due date - due date
- assignee
- parent etape - parent etape
- Render a real `/tasks` page instead of placeholder content. - Render a real `/tasks` page instead of placeholder content.
- Keep the page server-rendered with HTMX-driven form/modal flows. - Keep the page server-rendered with HTMX-driven form/modal flows.
@ -29,7 +30,6 @@ Build the first real tasks vertical slice in `go-backend`: owner-scoped storage,
- RBAC, collaborators, tablo sharing, or organization-level permissions - RBAC, collaborators, tablo sharing, or organization-level permissions
- Drag and drop - Drag and drop
- Reordering tasks or etapes - Reordering tasks or etapes
- Assignees
- Comments, attachments, or activity history - Comments, attachments, or activity history
- Separate API-only JSON endpoints - Separate API-only JSON endpoints
- Persisting a synthetic "Sans etape" record - Persisting a synthetic "Sans etape" record
@ -63,6 +63,7 @@ Columns:
- `title text not null` - `title text not null`
- `description text not null default ''` - `description text not null default ''`
- `status text not null` - `status text not null`
- `assignee_id uuid null references public.users(id) on delete set null`
- `is_etape boolean not null default false` - `is_etape boolean not null default false`
- `parent_task_id uuid null references public.tasks(id) on delete set null` - `parent_task_id uuid null references public.tasks(id) on delete set null`
- `due_date date null` - `due_date date null`
@ -74,6 +75,8 @@ Suggested indexes:
- active tablo task reads: - active tablo task reads:
- `(owner_id, tablo_id, deleted_at)` - `(owner_id, tablo_id, deleted_at)`
- assignee lookups:
- `(assignee_id, deleted_at)`
- etape grouping: - etape grouping:
- `(tablo_id, is_etape, deleted_at)` - `(tablo_id, is_etape, deleted_at)`
- child lookup: - child lookup:
@ -131,7 +134,7 @@ The trigger should reject:
- On success, returns refreshed `/tasks` content. - On success, returns refreshed `/tasks` content.
- On validation failure, returns modal or inline form content with status `422`. - On validation failure, returns modal or inline form content with status `422`.
- `PATCH /tasks/{taskID}` - `PATCH /tasks/{taskID}`
- Updates title, description, status, due date, and parent etape. - Updates title, description, status, due date, assignee, and parent etape.
- Must be owner-scoped. - Must be owner-scoped.
- On success, returns refreshed `/tasks` content or updated fragment. - On success, returns refreshed `/tasks` content or updated fragment.
- `DELETE /tasks/{taskID}` - `DELETE /tasks/{taskID}`
@ -161,6 +164,7 @@ Update:
- `description` - `description`
- `status` - `status`
- `due_date` - `due_date`
- `assignee_id`
- `parent_task_id` - `parent_task_id`
- for etapes: - for etapes:
- `parent_task_id` must remain null - `parent_task_id` must remain null
@ -221,6 +225,7 @@ Validation rules:
- title required - title required
- status required and must be one of the four allowed values - status required and must be one of the four allowed values
- malformed UUIDs return `400` - malformed UUIDs return `400`
- assignee, when present, must reference an existing user row
- etapes cannot have a parent - etapes cannot have a parent
- parent, when present, must be a valid owner-scoped etape in the same tablo - parent, when present, must be a valid owner-scoped etape in the same tablo
@ -250,6 +255,7 @@ Each displayed task row should support:
- optional description preview - optional description preview
- status indicator or control - status indicator or control
- due date when present - due date when present
- assignee when present
- parent etape display when relevant - parent etape display when relevant
- edit and delete actions - edit and delete actions
@ -259,6 +265,7 @@ Each displayed etape row or section should support:
- optional description - optional description
- status - status
- optional due date - optional due date
- optional assignee
- edit and delete actions - edit and delete actions
- nested child tasks - nested child tasks
@ -287,6 +294,7 @@ Minimum repository coverage:
- create etape - create etape
- list excludes soft-deleted rows - list excludes soft-deleted rows
- owner scoping is enforced - owner scoping is enforced
- assignee persists when set and clears to null when unset
- parent etape must belong to same owner - parent etape must belong to same owner
- parent etape must belong to same tablo - parent etape must belong to same tablo
- etape cannot point to a parent - etape cannot point to a parent
@ -304,6 +312,7 @@ Minimum handler coverage:
- `PATCH /tasks/{taskID}` updates description - `PATCH /tasks/{taskID}` updates description
- `PATCH /tasks/{taskID}` updates status - `PATCH /tasks/{taskID}` updates status
- `PATCH /tasks/{taskID}` updates due date - `PATCH /tasks/{taskID}` updates due date
- `PATCH /tasks/{taskID}` updates assignee
- `PATCH /tasks/{taskID}` updates parent etape - `PATCH /tasks/{taskID}` updates parent etape
- `DELETE /tasks/{taskID}` soft-deletes a regular task - `DELETE /tasks/{taskID}` soft-deletes a regular task
- `DELETE /tasks/{taskID}` soft-deletes an etape and moves children into runtime "Sans etape" - `DELETE /tasks/{taskID}` soft-deletes an etape and moves children into runtime "Sans etape"
@ -332,7 +341,8 @@ Minimum rendering assertions:
- sqlc queries and repository methods exist for owner-scoped task and etape CRUD. - sqlc queries and repository methods exist for owner-scoped task and etape CRUD.
- `/tasks` renders real owner data instead of placeholder content. - `/tasks` renders real owner data instead of placeholder content.
- Owners can create, list, update, and delete both tasks and etapes. - Owners can create, list, update, and delete both tasks and etapes.
- `PATCH /tasks/{taskID}` updates title, description, status, due date, and parent etape. - Tasks and etapes may have an optional `assignee_id`.
- `PATCH /tasks/{taskID}` updates title, description, status, due date, assignee, and parent etape.
- Deleting an etape causes its active child tasks to appear in runtime "Sans etape". - Deleting an etape causes its active child tasks to appear in runtime "Sans etape".
- No collaborator or RBAC behavior is introduced. - No collaborator or RBAC behavior is introduced.
- Targeted repository and handler tests cover the core flows and invariants. - Targeted repository and handler tests cover the core flows and invariants.