diff --git a/web/server.go b/web/server.go index 7125d8f..eeba124 100644 --- a/web/server.go +++ b/web/server.go @@ -169,6 +169,7 @@ func New(s *store.Store, logger *slog.Logger) (*Server, error) { "templates/layout.tmpl", "templates/tree.tmpl", "templates/tree_section.tmpl", + "templates/tree_card.tmpl", "templates/project_chip.tmpl", ) if err != nil { @@ -178,6 +179,7 @@ func New(s *store.Store, logger *slog.Logger) (*Server, error) { // Standalone tree-section template for HTMX fragment responses. treeSection, err := template.New("tree_section").Funcs(funcs).ParseFS(templatesFS, "templates/tree_section.tmpl", + "templates/tree_card.tmpl", "templates/project_chip.tmpl", ) if err != nil { @@ -440,8 +442,14 @@ func (s *Server) handleTree(w http.ResponseWriter, r *http.Request) { return } filter := ParseTreeFilter(r.URL.Query()) + viewSet := PageViewTypes("/") + view := ParseViewType(r.URL.Query(), viewSet) roots, orphans, total, orphanN, matched := applyTreeFilter(items, filter, linkKinds) counts := computeChipCounts(items, filter, linkKinds, tags) + // Phase 5i Slice B: the card view renders a flat grid of matched items + // (no tree structure). Build from items + filter directly rather than + // reusing the post-prune `roots` (which still keeps ancestors). + cardItems := flatMatchedItems(items, filter, linkKinds) data := map[string]any{ "Title": "tree", "Roots": roots, @@ -455,6 +463,9 @@ func (s *Server) handleTree(w http.ResponseWriter, r *http.Request) { "Projects": parentOptionsFromItems(items), "BasePath": "/", "ProjectChipTarget": "#tree-section", + "ViewType": view, + "ViewTypeChips": ViewTypeChips("/", filter, view), + "CardItems": cardItems, // ActiveTags kept for backwards-compat with the old template path; removed // after the template migrates fully. "ActiveTags": filter.Tags, diff --git a/web/server_test.go b/web/server_test.go index 6af9aaf..b7c8b85 100644 --- a/web/server_test.go +++ b/web/server_test.go @@ -295,6 +295,41 @@ func TestMultiParentBothPathsRouteToSameRow(t *testing.T) { } } +// TestTreeRendersCardGridWhenViewTypeIsCard verifies Phase 5i Slice B +// dispatch: `?view_type=card` renders the flat tile grid instead of the +// forest, and the view-type chip strip is present in either view. Unknown +// view_type values fall back to list with the chip-strip showing list as +// active. +func TestTreeRendersCardGridWhenViewTypeIsCard(t *testing.T) { + srv, pool := mustServer(t) + defer pool.Close() + h := srv.Routes() + // List view (default): forest markup expected; tree-card-grid absent. + _, listBody := get(t, h, "/") + if !strings.Contains(listBody, `