feat(views): Phase 5i slice C — kanban view_type with group_by chip strip
m's Q6 pick (2026-05-26): kanban groups the filtered set by `status`
(default) / `area` / `tag` / `management`. Read-only — drag-to-change
is parked. Adds the third view_type render on /tree (alongside list and
card from earlier slices); kanban is now unlocked in PageViewTypes("/").
New web/kanban.go owns BuildKanbanBoard + the per-dimension keyer +
column ordering (status: active/done/archived; management: mai/self/
external/unmanaged; area + tag: alphabetical). Within-column order:
pinned-first → updated_at desc → title.
ParseGroupBy + GroupByChips provide the URL-param hookup and the chip
strip rendered above the board. Multi-tag items appear in every tag
column they belong to (deliberate — the kanban surfaces overlap).
Render:
- handleTree builds the kanban board off the same flatMatchedItems the
card view consumes; cost is one extra grouping pass, no new DB hits.
- New templates/tree_kanban.tmpl: header chip strip + responsive
column board (horizontal scroll on overflow). Empty filtered set
surfaces a friendly nudge.
CSS additions cover the column / card layout; existing chip aesthetics
reused for the group-by toggle.
Test updates:
- view_type_test.go: slice B's "kanban locked on /" assertions tightened
to "kanban unlocked; calendar + timeline still locked on /" — slice C
is the unlock event for kanban.
- New kanban_test.go: per-dimension grouping (status, tag, area),
pinned-first ordering, parser fallback.
- server_test.go: end-to-end render — GET /?view_type=kanban produces
kanban-board markup + group-by chip strip; forest absent.
This commit is contained in:
@@ -229,6 +229,36 @@ table.classify input, table.classify select { width: 100%; }
|
||||
.tree-card-slug { font-size: 0.78em; }
|
||||
.tree-card-meta { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; font-size: 0.78em; }
|
||||
.tree-card-empty { grid-column: 1 / -1; padding: 24px; color: var(--muted); }
|
||||
/* Phase 5i Slice C — kanban columns + cards. */
|
||||
.kanban-controls { margin: 8px 0; }
|
||||
.groupby-chip {
|
||||
display: inline-block; font-size: 0.78em; padding: 1px 8px; border-radius: 999px;
|
||||
background: var(--surface); border: 1px solid var(--border); color: var(--muted); text-decoration: none;
|
||||
}
|
||||
.groupby-chip:hover { color: var(--fg); border-color: var(--accent); }
|
||||
.kanban-board {
|
||||
display: grid; gap: 12px; padding: 12px 0; overflow-x: auto;
|
||||
grid-auto-flow: column; grid-auto-columns: minmax(220px, 280px);
|
||||
}
|
||||
.kanban-column {
|
||||
background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
|
||||
padding: 8px 10px; display: flex; flex-direction: column; gap: 8px;
|
||||
min-height: 120px;
|
||||
}
|
||||
.kanban-col-head {
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
border-bottom: 1px solid var(--border); padding-bottom: 6px;
|
||||
}
|
||||
.kanban-col-label { font-weight: 500; }
|
||||
.kanban-cards { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.kanban-card {
|
||||
background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.kanban-card-title { font-weight: 500; color: var(--fg); text-decoration: none; display: block; }
|
||||
.kanban-card-title:hover { color: var(--accent); }
|
||||
.kanban-card-meta { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0 0; font-size: 0.78em; }
|
||||
.kanban-empty { padding: 24px; }
|
||||
#tree-filterbar small { opacity: 0.75; margin-left: 2px; }
|
||||
.tree-section .empty { padding: 24px; color: var(--muted); }
|
||||
.tree-section .clear { color: var(--bad); }
|
||||
|
||||
Reference in New Issue
Block a user