xtablo-source/.planning/phases/20-tablo-detail-kanban-restyle/20-RESEARCH.md
Arthur Belleville e18bf66dbf
docs(20): research phase — tablo detail page + kanban restyle
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 15:21:53 +02:00

28 KiB
Raw Blame History

Phase 20: Tablo Detail & Kanban Restyle - Research

Researched: 2026-05-18 Domain: Go + HTMX + Tailwind + templ — new page creation + CSS restyle Confidence: HIGH


Summary

Phase 20 has two intertwined goals: (1) create a tablo detail page that does not yet exist in the codebase, and (2) deliver the Figma-matching visual restyle for that page's kanban board and task cards. The current Go backend has no GET /tablos/{tabloID} route — clicking a tablo in the list has no detail view. This phase must build the page from scratch, then style it to match the UI-SPEC.

The existing kanban implementation lives exclusively inside the global /tasks page (tasks.templTasksKanbanLayout). That layout uses a CSS grid (grid-cols-1 md:grid-cols-2 lg:grid-cols-4) and the TaskCard component. The tablo detail kanban is a separate surface: a flex-row of fixed-width columns (18rem each, overflow-x: auto) scoped to one tablo, with Sortable.js drag-and-drop. The Phase 4 code (task creation, reorder handler, Sortable.js init) for the tablo-detail kanban currently exists only in router_test.go integration tests referencing ListTasksByTablo, implying it was coded in a previous milestone's tablo detail handler that was later replaced by go-backend/. All task mutation infrastructure (CreateTask, GetTaskByID, UpdateTask, SoftDeleteTask, ListTasksByTablo) exists in the repository layer and in the InMemory test repository — it just has no route-level handler wiring for the detail page.

The primary recommendation: build GET /tablos/{tabloID} as a new handler + templ component, add GET/POST /tablos/{tabloID}/tasks/... for task CRU within the detail context, implement the tab-bar, and apply the full CSS restyle from 20-UI-SPEC.md in app.css.

Primary recommendation: Create the tablo detail page as a new handler + view layer; reuse existing ListTasksByTablo and task mutation repo methods; apply all UI-SPEC CSS changes to app.css and the new templ components.


Architectural Responsibility Map

Capability Primary Tier Secondary Tier Rationale
Tablo detail route (GET /tablos/{tabloID}) Go handler (server) Fetches tablo record + tasks, builds view model, renders templ
Tab bar (Overview/Tasks/Files/Discussion/Events) templ template Static HTML tabs, HTMX push-url for navigation
Kanban board layout templ template (tablos_detail.templ) CSS (app.css) Column+card HTML; flex layout and card styles in CSS
Task CRUD within detail Go handler (server) CreateTask/UpdateTask/DeleteTask/ReorderTask scoped to tabloID
Drag-and-drop reorder Browser (Sortable.js) Go handler (reorder endpoint) JS-side drag; server persists new order via existing handler pattern
Progress bar computation Go handler (server) doneTasks/totalTasks computed at render time from task list
CSS restyle (cards, columns, header) CSS (app.css) Token-based rules; no new tokens needed
Files tab (restyle only) templ + CSS Existing ui.Table; add border/radius wrapper and header style

<phase_requirements>

Phase Requirements

ID Description Research Support
DETAIL-01 Tablo detail page (header, tasks section, etapes section, files table) is restyled to match Figma New GET /tablos/{tabloID} handler + TabloDetailPage templ component + CSS in app.css matching UI-SPEC tokens
TASK-01 Kanban board columns, task cards, and drag-and-drop are restyled to match Figma New tablo-scoped kanban templ + .task-card CSS block in app.css; Sortable.js init preserved verbatim
</phase_requirements>

Standard Stack

Core (existing — no new packages)

Library Version Purpose Why Standard
github.com/a-h/templ existing HTML components Entire view layer uses templ
Sortable.js CDN (existing) Drag-and-drop Already wired in router_test; Phase 4 integration
HTMX CDN v4.0.0-beta2 (existing) Partial page swaps Entire app uses HTMX for navigation
Tailwind CSS existing build Utility classes Used alongside custom CSS classes

No new packages needed

The UI-SPEC explicitly confirms: "No external registries. All components are templ + custom CSS." All needed UI primitives (ui.Button, ui.IconButton, ui.Badge, ui.Table) exist in backend/internal/web/ui/.

Installation: none required.


Package Legitimacy Audit

No new packages to install in this phase. The phase is purely a restyle + new page creation using existing infrastructure.

Packages removed due to slopcheck: none Packages flagged as suspicious: none


Architecture Patterns

System Architecture Diagram

Browser
  │ GET /tablos/{tabloID}
  ▼
Go handler: GetTabloDetailPage
  │ repo.ListTablos (find single tablo by ID + owner check)
  │ repo.ListTasksByTablo (fetch tasks for this tablo)
  │ compute progress: doneTasks / totalTasks * 100
  ▼
TabloDetailPageViewModel {tablo, tasks by status column, progress}
  │
  ▼
templ: TabloDetailPage
  ├── TabloDetailHeader (name h1, status badge, progress bar, owner row)
  ├── TabloTabBar (Overview | Tasks | Files | Discussion | Events)
  └── tab: Tasks → KanbanBoardSection
            ├── KanbanColumn × 4 (Todo / In Progress / In Review / Done)
            │     ├── ColumnHeader (title, count badge, "+ Add" link)
            │     └── TaskCardList (sortable-column)
            │           └── TaskCard × N (drag-handle, title, delete icon)
            └── TaskCreateInlineForm (htmx fragment, within column)

Browser
  Sortable.js init (DOMContentLoaded + htmx:afterSettle)
    handles: .sortable-column containers
    POST /tablos/{tabloID}/tasks/reorder (existing reorder pattern)

New files to create:

go-backend/internal/web/
├── views/
│   ├── tablo_detail.templ         # TabloDetailPage + all sub-components
│   └── tablo_detail_view.go       # TabloDetailViewModel + helpers
├── handlers/
│   └── tablo_detail.go            # GetTabloDetailPage handler

Modified files:

go-backend/
├── router.go                      # Add GET /tablos/{tabloID}
├── internal/web/ui/app.css        # All CSS changes from UI-SPEC delta table

Pattern 1: Tablo Detail Handler (new page + HTMX swap)

// Source: existing handler pattern from handlers/auth.go + handlers/tablos.go
func (h *AuthHandler) GetTabloDetailPage() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        user, ok := h.authenticatedUser(r.Context(), r)
        if !ok {
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }

        tabloID, err := uuid.Parse(r.PathValue("tabloID"))
        if err != nil {
            http.Error(w, "invalid tablo id", http.StatusBadRequest)
            return
        }

        // Reuse ListTablos to find the owned tablo (no GetTabloByID sqlc query exists)
        tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{OwnerID: user.ID})
        if err != nil {
            http.Error(w, "failed to load tablos", http.StatusInternalServerError)
            return
        }
        tablo, ok := findTabloByID(tablos, tabloID)
        if !ok {
            http.Error(w, "tablo not found", http.StatusNotFound)
            return
        }

        // ListTasksByTablo is defined in the repo and InMemory repo
        taskRepo := h.repo.(interface {
            ListTasksByTablo(context.Context, ListTasksByTabloInput) ([]TaskRecord, error)
        })
        tasks, err := taskRepo.ListTasksByTablo(r.Context(), ListTasksByTabloInput{
            OwnerID: user.ID,
            TabloID: tabloID,
        })
        if err != nil {
            http.Error(w, "failed to load tasks", http.StatusInternalServerError)
            return
        }

        vm := views.NewTabloDetailViewModel(tablo, tasks)
        content := views.TabloDetailPage(vm)

        w.Header().Set("Content-Type", "text/html; charset=utf-8")
        var renderErr error
        if isHXRequest(r) {
            renderErr = views.DashboardContentSwap("/tablos/"+tabloID.String(), tablos, content).Render(r.Context(), w)
        } else {
            renderErr = views.DashboardPage("/tablos/"+tabloID.String(), tablos, content).Render(r.Context(), w)
        }
        if renderErr != nil {
            http.Error(w, "failed to render tablo detail", http.StatusInternalServerError)
        }
    }
}

[ASSUMED] — GetTabloByID does not exist in sqlc queries; using ListTablos + findTabloByID is the current pattern from GetEditTabloModal. For a large tablo list this is an O(n) scan. At v1 scale this is acceptable.

Pattern 2: Progress Computation

// Source: [ASSUMED] — derived from existing tabloStatusPresentation pattern
func computeTabloProgress(tasks []TaskRecord) int {
    total := len(tasks)
    if total == 0 {
        return 0
    }
    var done int
    for _, t := range tasks {
        if t.Status == TaskStatusDone {
            done++
        }
    }
    return (done * 100) / total
}

Pattern 3: Kanban Column templ (new, scoped to tablo detail)

// Source: UI-SPEC + existing TasksKanbanLayout pattern
templ TabloDetailKanbanColumn(col TabloDetailColumnView) {
    <div class="tablo-kanban-column" data-status={ col.Status }>
        <div class="tablo-kanban-column-header">
            <span class="tablo-kanban-column-title">{ col.Label }</span>
            <span class="tablo-kanban-task-count">{ len(col.Tasks) }</span>
            <a href="#" class="tablo-kanban-add-link" hx-get={ col.CreateHref }
               hx-target={ "#create-zone-" + col.Status } hx-swap="innerHTML">
               + Ajouter
            </a>
        </div>
        <div id={ "task-list-" + col.Status } class="task-list sortable-column"
             data-status={ col.Status }>
            for _, task := range col.Tasks {
                @TabloDetailTaskCard(task)
            }
        </div>
        <div id={ "create-zone-" + col.Status }></div>
    </div>
}

Pattern 4: Drag Handle (opacity-based hover)

/* Source: UI-SPEC Component Delta section */
.task-drag-handle {
  color: var(--color-text-faint);
  cursor: grab;
  font-size: 1rem;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.task-card:hover .task-drag-handle {
  opacity: 1;
}

Pattern 5: Sortable.js init (preserved from Phase 4)

// Source: [ASSUMED] — Sortable.js init pattern from STATE.md Phase 4 decisions
// Must fire on DOMContentLoaded AND htmx:afterSettle
function initSortable() {
  document.querySelectorAll('.sortable-column').forEach(function(el) {
    if (el._sortable) return; // idempotent
    el._sortable = Sortable.create(el, {
      group: 'tablo-tasks',
      animation: 150,
      handle: '.task-drag-handle',
      draggable: '.task-card',
      onEnd: function(evt) {
        // Update hidden reorder form + submit
        document.querySelector('#reorder-form').requestSubmit();
      }
    });
  });
}
document.addEventListener('DOMContentLoaded', initSortable);
document.addEventListener('htmx:afterSettle', initSortable);

The UI-SPEC states: "Sortable.js initialization on DOMContentLoaded and htmx:afterSettle is PRESERVED verbatim."

Anti-Patterns to Avoid

  • Using display: none for drag handle / delete icon: UI-SPEC requires opacity: 0 → 1 (not display-toggling) to preserve layout during hover.
  • Mixing global tasks kanban with tablo-detail kanban: The TasksKanbanLayout in tasks.templ is for the global /tasks page and must remain unchanged. The tablo detail gets a new separate TabloDetailKanbanLayout.
  • Adding a task-view switcher (Board/List/Gantt): UI-SPEC explicitly defers the inner task-view switcher to Phase 21. Do not add it.
  • Using inline Tailwind for the card restyle: The UI-SPEC defines .task-card as a named CSS class in app.css. Inline Tailwind classes on these components would conflict with the CSS cascade.
  • Routing task create/delete to global /tasks endpoints: Task mutations within the detail page must include tabloID context so the response re-renders the detail page, not the global tasks page.

Don't Hand-Roll

Problem Don't Build Use Instead Why
Drag-and-drop column reorder Custom JS drag Sortable.js (already included) Phase 4 already wired; re-implementing is regression risk
Status badge pill Custom badge HTML ui.Badge with badgeVariantForTone Already has all required tone variants in badge.css
Modal dialog Raw HTML <dialog> ui.Modal Already exists in modal.templ + modal.css
Progress bar math Separate API endpoint Compute inline in handler from task list Task list already fetched; no extra query needed
Table wrapper Custom <table> ui.Table + wrapper div ui.Table handles shell; Phase 20 adds border/radius wrapper div around it

Common Pitfalls

Pitfall 1: No GetTabloByID sqlc query

What goes wrong: Code tries to call h.repo.GetTabloByID(...) which doesn't exist in the AuthRepository interface or sqlc. Why it happens: The repository only exposes ListTablos (filtered list). No single-record lookup was added in earlier phases. How to avoid: Use findTabloByID(tablos, tabloID) after ListTablos. This is the same pattern used in GetEditTabloModal. Warning signs: Compiler error: AuthRepository has no method GetTabloByID.

Pitfall 2: ListTasksByTablo not in AuthRepository interface

What goes wrong: h.repo is typed as AuthRepository which does not include ListTasksByTablo. The method is only on taskPageRepository interface (defined in handlers/tasks.go). Why it happens: AuthRepository in handlers/auth.go was not extended with task methods. How to avoid: Either (a) add a TabloDetailRepository interface combining the needed methods, or (b) type-assert h.repo.(taskDetailRepository) the same way renderTasksPage does. Pattern (b) requires the InMemoryAuthRepository to also implement ListTasksByTablo — it already does (in_memory_auth_repository.go:168). Warning signs: Compiler error about interface satisfaction.

Pitfall 3: HTMX active-path mismatch in sidebar

What goes wrong: The sidebar nav item for the current tablo is not highlighted when viewing /tablos/{tabloID}. Why it happens: sidebarNavItemID uses the href to generate DOM IDs; tablo-detail is a sub-path that doesn't match any primary nav item's Href. How to avoid: Pass activePath as /tablos (the parent) for all tablo detail pages so the sidebar "Tablos" nav item remains active.

Pitfall 4: Sortable.js double-init after HTMX swap

What goes wrong: After an HTMX swap refreshes the kanban (e.g., after task create), Sortable.js is initialized again, creating a second instance on already-initialized containers, causing duplicate handlers. Why it happens: htmx:afterSettle fires every time a swap completes; naive re-init doesn't check for existing instances. How to avoid: Guard init with if (el._sortable) return; — set el._sortable on first init.

Pitfall 5: progressInlineStyle uses var(--project-color)

What goes wrong: The progress bar fill color on the tablo detail page matches the project color instead of var(--color-brand-primary) as specified. Why it happens: The existing .project-progress-bar rule uses background: var(--project-color, var(--color-project-fallback)) — inherited from Phase 19 tablo cards. How to avoid: UI-SPEC says "Change fill color from var(--project-color) to var(--color-brand-primary)". Apply a scoped override for the detail page header progress bar: .tablo-detail-header .project-progress-bar { background: var(--color-brand-primary); } — or use a separate CSS class tablo-progress-bar on the detail page's progress element.

Pitfall 6: Tablo Status vs task Status confusion

What goes wrong: The tablo status field (todo/in_progress/done) is used to drive the progress bar value (0/50/100) on tablo cards — but this is NOT the task completion progress. The tablo detail header's progress must be computed from actual task statuses (done tasks / total tasks), not the tablo's own status field. Why it happens: buildTabloCardViews uses tabloStatusPresentation(tablo.Status) to set Progress: 50 for in-progress tablos — that's a placeholder. How to avoid: In the detail handler, compute progress = computeTabloProgress(tasks) from real task records.


Code Examples

CSS: .task-card new block (from UI-SPEC)

/* Source: 20-UI-SPEC.md Surfaces: Kanban Board → Task cards */
.task-card {
  background: var(--color-surface-default);
  border: 1px solid var(--color-border-default);
  border-radius: 8px;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-inline: 12px;
  padding-block: 8px;
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
}

.task-card:hover {
  border-color: var(--color-border-strong);
  box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
}

CSS: Kanban column border-radius change

/* Source: 20-UI-SPEC.md Component Delta table — change 1rem → 0.75rem */
.tablo-kanban-column {
  background: var(--color-surface-default);
  border: 1px solid var(--color-border-default);
  border-radius: 0.75rem;  /* was 1rem in tasks-section */
  overflow: hidden;
  width: 18rem;
  flex-shrink: 0;
}

CSS: Task list gap

/* Source: 20-UI-SPEC.md Component Delta — add gap: 8px; padding: 8px */
.task-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
}

CSS: Kanban board container

/* Source: 20-UI-SPEC.md Surfaces: Kanban Board → Board container */
.tablo-kanban-board {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 16px;
}

CSS: Empty column state

/* Source: 20-UI-SPEC.md Surfaces: Kanban Board → Empty column state */
.tablo-kanban-empty {
  color: var(--color-text-faint);
  font-size: 0.875rem;
  padding: 24px 16px;
  text-align: center;
}

CSS: Files table wrapper restyle

/* Source: 20-UI-SPEC.md Surfaces: Files Table */
.tablo-files-table-wrapper {
  border: 1px solid var(--color-border-default);
  border-radius: 12px;
  overflow: hidden;
}

.tablo-files-table-wrapper thead tr {
  background: var(--color-surface-muted);
}

.tablo-files-table-wrapper thead th {
  color: var(--color-text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.tablo-files-table-wrapper tbody tr {
  border-bottom: 1px solid var(--color-border-default);
}

.tablo-files-table-wrapper tbody tr:hover {
  background: var(--color-surface-subtle);
}

templ: Tablo detail header layout

// Source: [ASSUMED] — pattern derived from UI-SPEC header spec
templ TabloDetailHeader(vm TabloDetailHeaderView) {
    <header class="tablo-detail-header">
        <div class="tablo-detail-title-row">
            <div class="tablo-detail-avatar" style={ projectColorVariableStyle(vm.Color) }>
                { vm.Initial }
            </div>
            <h1 class="tablo-detail-title">{ vm.Name }</h1>
        </div>
        <div class="tablo-metadata-row">
            <span class="tablo-meta-segment">
                <!-- owner avatar 24×24 + name -->
            </span>
            <span class="tablo-meta-segment">
                @ActionIcon("calendar")
                if vm.DueDate != "" { vm.DueDate } else { "—" }
            </span>
            <span class="tablo-meta-segment">
                @ui.Badge(ui.BadgeProps{Label: vm.StatusLabel, Variant: badgeVariantForTone(vm.StatusTone)})
            </span>
            <span class="tablo-meta-segment tablo-meta-progress">
                <div class="project-progress-track" style="min-width:120px">
                    <div class="tablo-detail-progress-bar" style={ progressInlineStyle(vm.Progress) }></div>
                </div>
                <strong>{ vm.ProgressLabel }</strong>
            </span>
        </div>
    </header>
}

State of the Art

Old Approach Current Approach When Changed Impact
Tablo progress = tablo.Status mapped to 0/50/100 Compute from actual task done/total ratio Phase 20 Detail page shows real progress
No tablo detail page GET /tablos/{tabloID} with tab bar Phase 20 First time users can navigate into a tablo
Global kanban (flat grid, grid-cols-4) Tablo-scoped kanban (flex row, overflow-x: auto) Phase 20 Detail page kanban vs global tasks page kanban

Deprecated/outdated:

  • .project-progress-bar { background: var(--project-color) } for detail page use: replaced by var(--color-brand-primary) per UI-SPEC.
  • .tasks-section { border-radius: 1rem } in cards context: changed to 0.75rem for kanban columns.

Assumptions Log

# Claim Section Risk if Wrong
A1 GetTabloByID sqlc query does not exist; must use ListTablos + findTabloByID Pitfall 1 If a GetTabloByID query was added via just generate after last codebase read, handler can be simplified — low risk either way
A2 ListTasksByTablo is not in AuthRepository interface; needs type assertion or new interface Pitfall 2 If someone added it to the interface, no type-assert needed
A3 Sortable.js CDN is already loaded on the tablo detail page Pattern 5 If Sortable.js is not in DashboardPage head, it must be added
A4 The tablo detail page has no existing route and no existing templ component Standard Stack If a partial implementation exists in a branch, avoid duplication
A5 Files tab shows files from the tablo's R2 bucket; no file listing query currently exists for tablo-scoped files Files Table section Phase 20 UI-SPEC says "restyle only" — implies files list already renders. If no file data available, the files tab renders with empty state only

Open Questions

  1. Tablo detail route navigation from tablo cards

    • What we know: TabloGridCard and TabloListRow in tablos.templ have no hx-get to the detail page — they have edit/delete buttons only.
    • What's unclear: Should clicking the card title navigate to detail? Or is there a separate "View" button?
    • Recommendation: Make the card title/avatar an hx-get="/tablos/{id}" link, consistent with the linear-style navigation pattern.
  2. Files tab data for tablo detail

    • What we know: The UI-SPEC says "restyle only" for the files table. No file listing sqlc query for tablo-scoped files was found.
    • What's unclear: Are files accessible? Is there a ListFilesByTablo repo method?
    • Recommendation: If no files query exists, render files tab with empty state (ui.EmptyState) only — no blocking dependency for TASK-01 or DETAIL-01.
  3. Owner display for tablo detail header

    • What we know: tablo.OwnerID exists; GetPublicUserByID exists in AuthRepository.
    • What's unclear: Whether the handler should fetch the owner record for display.
    • Recommendation: Fetch owner display name in handler via GetPublicUserByID for the metadata row. Falls back to "Propriétaire" if fetch fails.

Environment Availability

Phase is code/CSS only. No new external tools or services required.

Step 2.6: SKIPPED — No external dependencies beyond existing Go toolchain, templ, and Tailwind build already in place.


Validation Architecture

Test Framework

Property Value
Framework Vitest (frontend not applicable) / Go stdlib testing + net/http/httptest
Config file none (Go stdlib test runner)
Quick run command go test ./... -run TestTabloDetail -count=1
Full suite command go test ./... -count=1

Phase Requirements → Test Map

Req ID Behavior Test Type Automated Command File Exists?
DETAIL-01 GET /tablos/{tabloID} returns 200 with tablo name in response unit/integration go test ./... -run TestGetTabloDetailPage -count=1 Wave 0
DETAIL-01 Progress bar percentage rendered as {N}% in response unit go test ./... -run TestTabloDetailProgress -count=1 Wave 0
DETAIL-01 Status badge label rendered in metadata row unit go test ./... -run TestTabloDetailStatusBadge -count=1 Wave 0
TASK-01 Kanban columns render 4 status columns unit go test ./... -run TestTabloDetailKanbanColumns -count=1 Wave 0
TASK-01 Task cards contain .task-card class unit go test ./... -run TestTabloDetailTaskCardMarkup -count=1 Wave 0
TASK-01 Drag handle has opacity:0 at rest (CSS class present) unit go test ./... -run TestTabloDetailDragHandle -count=1 Wave 0

Sampling Rate

  • Per task commit: go test ./... -run TestTabloDetail -count=1
  • Per wave merge: go test ./... -count=1
  • Phase gate: Full suite green before /gsd:verify-work

Wave 0 Gaps

  • go-backend/internal/web/handlers/tablo_detail_test.go — covers all DETAIL-01 + TASK-01 handler tests
  • go-backend/router_test.go additions — integration-level smoke test for GET /tablos/{tabloID}

(Existing test infrastructure in handlers/tablos_test.go provides the scaffold pattern to follow.)


Security Domain

Applicable ASVS Categories

ASVS Category Applies Standard Control
V2 Authentication yes authenticatedUser(r.Context(), r) — existing session check, same as all other handlers
V3 Session Management yes existing xtablo_session cookie pattern
V4 Access Control yes findTabloByID filters by OwnerID — prevents accessing other users' tablos
V5 Input Validation yes uuid.Parse(r.PathValue("tabloID")) guards invalid IDs
V6 Cryptography no No crypto in this phase

Known Threat Patterns for this stack

Pattern STRIDE Standard Mitigation
IDOR — access another user's tablo by guessing UUID Elevation of Privilege findTabloByID only returns tablos owned by authenticated user's ID
Task mutation across tablo boundary Tampering ListTasksByTablo includes OwnerID in query; UpdateTask/DeleteTask handlers enforce ownership

Sources

Primary (HIGH confidence)

  • /go-backend/internal/web/ui/base.css — all CSS custom property tokens confirmed
  • /go-backend/internal/web/ui/app.css — existing .task-row, .project-progress-bar, .tasks-section CSS rules
  • /go-backend/internal/web/views/tasks.templ — existing TasksKanbanLayout, TaskCard, TasksKanbanColumnView patterns
  • /go-backend/internal/web/views/tablos.templ — existing TabloGridCard, header/progress patterns
  • /go-backend/internal/web/handlers/tablos.gofindTabloByID, tabloStatusPresentation, existing handler patterns
  • /go-backend/internal/web/handlers/auth.goAuthRepository interface definition (no ListTasksByTablo)
  • /go-backend/router.go — confirmed no GET /tablos/{tabloID} route exists
  • /go-backend/internal/tablos/model.go — tablo model fields confirmed
  • /go-backend/internal/tasks/model.go — task status enums, ListByTabloInput
  • .planning/phases/20-tablo-detail-kanban-restyle/20-UI-SPEC.md — locked visual decisions

Secondary (MEDIUM confidence)

  • .claude/skills/sketch-findings-xtablo-source/SKILL.md — design direction and CSS patterns verified against existing base.css tokens

Tertiary (LOW confidence)

  • [ASSUMED] items in the Assumptions Log above — architectural inferences from existing handler patterns

Metadata

Confidence breakdown:

  • Standard stack: HIGH — confirmed from codebase; no new packages
  • Architecture: HIGH — detail page gap confirmed, handler patterns confirmed, CSS tokens confirmed
  • Pitfalls: HIGH — confirmed from direct code inspection (interface gaps, CSS overrides)

Research date: 2026-05-18 Valid until: 2026-06-17 (30-day window; stable stack)