From 63882be278f4aa1ede55f9758a6f2966d7a18a6d Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Fri, 8 May 2026 21:33:51 +0200 Subject: [PATCH] docs: add go-backend tablos index CRUD design --- ...-08-go-backend-tablos-index-crud-design.md | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-08-go-backend-tablos-index-crud-design.md diff --git a/docs/superpowers/specs/2026-05-08-go-backend-tablos-index-crud-design.md b/docs/superpowers/specs/2026-05-08-go-backend-tablos-index-crud-design.md new file mode 100644 index 0000000..801b01c --- /dev/null +++ b/docs/superpowers/specs/2026-05-08-go-backend-tablos-index-crud-design.md @@ -0,0 +1,287 @@ +# Go Backend Tablos Index CRUD Design + +**Date:** 2026-05-08 + +**Goal** + +Build the first full `tablos` vertical slice in the Go rewrite: an authenticated `/tablos` page with server-rendered list, functional filtering/search/view controls, modal-based create, and soft delete. + +**Scope** + +- Add a real `tablos` table to the Go backend schema with: + - `id` + - `owner_id` + - `name` + - `status` + - `created_at` + - `updated_at` + - `deleted_at` +- Render a real `/tablos` page in the rewrite app instead of placeholder content. +- Support listing the current user's non-deleted tablos. +- Support creating a tablo from a modal. +- Support soft deleting a tablo with `DELETE /tablos/:id`. +- Make grid/list toggle, search, and status filters functional. +- Keep the page server-rendered with HTMX partial updates. + +**Out of Scope** + +- Single project detail page at `/tablos/:id` +- Updating an existing tablo +- Multi-user sharing, access levels, or organization-owned tablos +- Database-level enum or constraint for `status` +- Custom frontend JavaScript beyond HTMX +- Client-side persistence for page UI state + +**Architecture** + +The feature should live entirely inside the Go rewrite stack: + +- Postgres schema in `go-backend/internal/db/schema.sql` +- sqlc queries in `go-backend/internal/db/queries.sql` +- repository methods in `go-backend/internal/db/repository.go` +- HTTP handlers in `go-backend/internal/web/handlers/` +- `templ` views in `go-backend/internal/web/views/` +- route registration in `go-backend/router.go` + +The page remains server-rendered. HTMX is used only to refresh the `/tablos` content region and modal content without introducing a SPA data layer. + +**Data Model** + +The `tablos` table should store: + +- `id uuid primary key` +- `owner_id uuid not null references public.users(id) on delete cascade` +- `name 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` + +The database stores `status` as plain text. The allowed values are enforced in Go, not in Postgres. + +Initial allowed statuses: + +- `todo` +- `in_progress` +- `done` + +For this slice, create always writes `todo`. + +`updated_at` should be maintained by application writes in the repository layer. Reads must always exclude rows where `deleted_at` is non-null. + +**Runtime Validation** + +Status validation belongs to Go code through a small domain type and helpers, for example: + +- `type TabloStatus string` +- constants for `todo`, `in_progress`, `done` +- parse/validation helpers for query params and any future writes + +The handler layer should treat invalid incoming `status` values as bad input for mutations and safe defaults for filter state. + +**Route Design** + +- `GET /tablos` + - Returns the full dashboard page for normal requests. + - Returns the swapped `/tablos` content fragment for HTMX requests. + - Accepts query params: + - `view=grid|list` + - `q=` + - `status=all|todo|in_progress|done` +- `POST /tablos` + - Creates a new tablo for the authenticated user. + - Reads form data from the modal. + - Sets `status=todo` in Go. + - On success, returns refreshed `/tablos` content in the current query state. + - On validation failure, returns modal content with inline errors and status `422`. +- `DELETE /tablos/{id}` + - Verifies ownership. + - Sets `deleted_at` and updates `updated_at`. + - Returns refreshed `/tablos` content in the current query state. + +The delete request should preserve the current filter/search/view state by including the active query values in the HTMX request URL or values payload. + +**Repository Design** + +Extend the current repository abstraction beyond auth concerns so tablos can be stored and queried through explicit methods. + +Minimum methods: + +- list tablos for a user with optional filters +- create tablo +- soft delete tablo owned by a user + +List semantics: + +- scope by `owner_id = current user` +- filter out `deleted_at is not null` +- optional case-insensitive search on `name` +- optional status filter when status is not `all` +- deterministic ordering, preferably newest first via `created_at desc` + +Delete semantics: + +- only soft delete the row when `owner_id` matches the authenticated user +- treat already-deleted rows as not found for this slice + +**UI Contract** + +The `/tablos` page should follow the user-provided structure and class naming as the visual contract for the rewrite. + +Top-level page shell: + +```html +
...
+``` + +The page should include: + +- header row with `Mes Projets` +- a primary `Nouveau projet` button +- view toggle tabs for grid and list +- a search field +- status filter buttons for: + - `Tous` + - `Pas commencé` + - `En cours` + - `Terminé` +- a grid rendering mode matching the provided card structure +- a list rendering mode using the same dataset and controls + +The layout, spacing, and class direction should track the supplied HTML closely rather than reusing the older app structure. + +**Modal Create Flow** + +`Nouveau projet` opens a modal dialog. + +The modal should contain: + +- a required `name` field +- submit action +- cancel/close action +- inline validation message region + +The modal does not expose status in this slice because new tablos always start as `todo`. + +HTMX behavior: + +- open modal via server-rendered fragment or pre-rendered hidden dialog pattern +- submit modal with `POST /tablos` +- on success: + - refresh the tablos page content + - close the modal by returning the page fragment without an error state +- on failure: + - keep the modal open + - render inline validation feedback inside the modal + +**Functional Controls** + +The controls in the provided `/tablos` layout must be functional in this slice. + +View toggle: + +- URL-backed via `view=grid|list` +- HTMX `GET` updates the content region +- active tab styling reflects current state + +Search: + +- URL-backed via `q` +- server-side filtering +- HTMX submission with debounce +- preserves current `view` and `status` + +Status filters: + +- URL-backed via `status` +- values: + - `all` + - `todo` + - `in_progress` + - `done` +- preserve current `view` and `q` + +Safe defaults: + +- missing or invalid `view` defaults to `grid` +- missing or invalid `status` defaults to `all` +- empty `q` means no text filter + +**Rendering States** + +The page should render clear states for: + +- empty result set with helpful copy and the same page controls still visible +- populated grid view +- populated list view +- validation failure in the create modal +- delete failure when the resource is missing or not owned + +For this slice, inline and local feedback is preferred over a global toast system. + +**HTMX Response Strategy** + +Normal requests return the full dashboard page. + +HTMX requests return only the relevant fragment(s): + +- `/tablos` content swap for page refresh after search/filter/view changes +- modal content fragment for create validation failures +- optionally out-of-band updates if needed for modal close behavior + +The implementation should keep fragment boundaries explicit so future `/tablos/:id` work can reuse the same dashboard composition pattern. + +**Testing Strategy** + +Use TDD across repository and handler layers. + +Minimum repository coverage: + +- create tablo sets `status=todo` +- list excludes soft-deleted rows +- list filters by status +- list filters by search query +- soft delete only affects the targeted owned row + +Minimum handler coverage: + +- authenticated `GET /tablos` renders full page +- authenticated HTMX `GET /tablos` renders content fragment +- `GET /tablos` honors `view`, `q`, and `status` +- `POST /tablos` with valid input creates and re-renders content +- `POST /tablos` with empty name returns `422` and modal errors +- `DELETE /tablos/{id}` soft deletes and re-renders content +- deleting another user's tablo fails + +HTML assertions should verify: + +- the page contains the agreed `Mes Projets` heading +- the `Nouveau projet` trigger exists +- grid and list modes produce distinguishable markup +- filter controls reflect active state +- modal validation feedback renders inline + +**Risks and Mitigations** + +- Extending an auth-focused repository into app data could blur responsibilities. + - Mitigation: keep tablo methods explicit and narrow, and refactor to a broader app repository only if a second domain forces it. +- HTMX modal behavior can become messy if success and error fragments are not clearly separated. + - Mitigation: define a small set of stable fragment boundaries before implementation. +- URL-backed controls can lose state across create/delete if query params are not preserved. + - Mitigation: make current query state part of all HTMX requests that mutate or refresh the page. + +**Acceptance Criteria** + +- The Go rewrite has a real `tablos` table with the agreed columns. +- Visiting `/tablos` while authenticated shows a real projects page, not placeholder text. +- The page visually follows the supplied HTML structure and class direction. +- `Nouveau projet` opens a modal. +- Creating a tablo stores it for the authenticated user with `status=todo`. +- Deleting a tablo uses `DELETE /tablos/{id}` and sets `deleted_at`. +- Soft-deleted tablos no longer appear in reads. +- Grid/list toggle is functional. +- Search is functional. +- Status filters are functional. +- All state is server-driven and URL-backed where applicable. +- No custom application JavaScript is added for this slice. +- Targeted tests pass.