28 KiB
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.templ → TasksKanbanLayout). 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)
Recommended Project Structure
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: nonefor drag handle / delete icon: UI-SPEC requiresopacity: 0 → 1(not display-toggling) to preserve layout during hover. - Mixing global tasks kanban with tablo-detail kanban: The
TasksKanbanLayoutintasks.templis for the global/taskspage and must remain unchanged. The tablo detail gets a new separateTabloDetailKanbanLayout. - 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-cardas a named CSS class inapp.css. Inline Tailwind classes on these components would conflict with the CSS cascade. - Routing task create/delete to global
/tasksendpoints: Task mutations within the detail page must includetabloIDcontext 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 byvar(--color-brand-primary)per UI-SPEC..tasks-section { border-radius: 1rem }in cards context: changed to0.75remfor 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
-
Tablo detail route navigation from tablo cards
- What we know:
TabloGridCardandTabloListRowintablos.templhave nohx-getto 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.
- What we know:
-
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
ListFilesByTablorepo 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.
-
Owner display for tablo detail header
- What we know:
tablo.OwnerIDexists;GetPublicUserByIDexists inAuthRepository. - What's unclear: Whether the handler should fetch the owner record for display.
- Recommendation: Fetch owner display name in handler via
GetPublicUserByIDfor the metadata row. Falls back to "Propriétaire" if fetch fails.
- What we know:
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 testsgo-backend/router_test.goadditions — integration-level smoke test forGET /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-sectionCSS rules/go-backend/internal/web/views/tasks.templ— existingTasksKanbanLayout,TaskCard,TasksKanbanColumnViewpatterns/go-backend/internal/web/views/tablos.templ— existingTabloGridCard, header/progress patterns/go-backend/internal/web/handlers/tablos.go—findTabloByID,tabloStatusPresentation, existing handler patterns/go-backend/internal/web/handlers/auth.go—AuthRepositoryinterface definition (noListTasksByTablo)/go-backend/router.go— confirmed noGET /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 existingbase.csstokens
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)