fix(tui): keep scoped archive groups separate

This commit is contained in:
Vincent Koc 2026-05-04 00:49:09 -07:00
parent 8fcbbda232
commit 9abbe55576
No known key found for this signature in database
2 changed files with 66 additions and 18 deletions

View File

@ -2569,25 +2569,30 @@ func (m model) groupFields(item Item) (key, title, kind, scope string) {
case LayoutChat:
if m.groupMode == groupByAuthor {
if author := strings.TrimSpace(itemAuthor(item)); author != "" {
return "author:" + author, displayLabel(author), "person", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("author", author, scope), displayLabel(author), "person", scope
}
}
if m.groupMode == groupByThread {
if thread := threadKey(item); thread != "" {
scope := strings.TrimSpace(item.Scope)
title := firstNonEmpty(item.Title, thread)
return "thread:" + thread, displayLabel(title), "thread", strings.TrimSpace(item.Scope)
return scopedGroupKey("thread", thread, scope), displayLabel(title), "thread", scope
}
}
if container := strings.TrimSpace(item.Container); container != "" {
return "container:" + container, displayLabel(container), "channel", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("container", container, scope), displayLabel(container), "channel", scope
}
if author := strings.TrimSpace(itemAuthor(item)); author != "" {
return "author:" + author, displayLabel(author), "person", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("author", author, scope), displayLabel(author), "person", scope
}
case LayoutDocument:
if m.groupMode == groupByContainer {
if container := strings.TrimSpace(item.Container); container != "" {
return "container:" + container, displayLabel(container), "database", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("container", container, scope), displayLabel(container), "database", scope
}
}
if m.groupMode == groupByScope {
@ -2596,10 +2601,12 @@ func (m model) groupFields(item Item) (key, title, kind, scope string) {
}
}
if parent := strings.TrimSpace(item.ParentID); parent != "" {
return "parent:" + parent, displayLabel(parent), "parent", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("parent", parent, scope), displayLabel(parent), "parent", scope
}
if container := strings.TrimSpace(item.Container); container != "" {
return "container:" + container, displayLabel(container), "database", strings.TrimSpace(item.Scope)
scope := strings.TrimSpace(item.Scope)
return scopedGroupKey("container", container, scope), displayLabel(container), "database", scope
}
}
for _, value := range []struct {
@ -2619,6 +2626,15 @@ func (m model) groupFields(item Item) (key, title, kind, scope string) {
return "row:" + title, displayLabel(title), firstNonEmpty(itemKind(item), "row"), strings.TrimSpace(item.Scope)
}
func scopedGroupKey(kind, value, scope string) string {
value = strings.TrimSpace(value)
scope = strings.TrimSpace(scope)
if scope == "" {
return kind + ":" + value
}
return kind + ":" + scope + "/" + value
}
func compareGroups(left, right itemGroup, mode sortMode) (bool, bool) {
switch mode {
case sortCount:
@ -3438,11 +3454,14 @@ func (m model) groupColumns(width int) []tableColumn {
}
func (m model) shouldShowGroupScopeColumn() bool {
if m.layoutPreset == LayoutChat {
return false
}
seen := map[string]struct{}{}
for _, group := range m.groups {
if strings.TrimSpace(group.Scope) != "" {
scope := strings.TrimSpace(group.Scope)
if scope == "" {
continue
}
seen[strings.ToLower(scope)] = struct{}{}
if m.layoutPreset != LayoutChat || len(seen) > 1 {
return true
}
}

View File

@ -400,6 +400,26 @@ func TestGroupColumnsOmitEmptyScope(t *testing.T) {
}
}
func TestChatGroupsDoNotMergeSameChannelAcrossScopes(t *testing.T) {
m := newModel(Options{Layout: LayoutChat, Items: []Item{
Row{Kind: "message", Scope: "workspace-a", Container: "general", Title: "one", CreatedAt: "2026-05-02T12:00:00Z"}.ItemForLayout(LayoutChat),
Row{Kind: "message", Scope: "workspace-b", Container: "general", Title: "two", CreatedAt: "2026-05-02T13:00:00Z"}.ItemForLayout(LayoutChat),
}})
if len(m.groups) != 2 {
t.Fatalf("same channel names in different scopes should stay separate: %#v", m.groups)
}
columns := m.groupColumns(90)
foundScope := false
for _, column := range columns {
if column.Key == "scope" {
foundScope = true
}
}
if !foundScope {
t.Fatalf("multi-scope chat groups should expose the scope column: %#v", columns)
}
}
func TestVeryNarrowPanesStillShowCompactColumns(t *testing.T) {
group := itemGroup{Kind: "channel", Count: 18, Latest: "2026-05-02T12:00:00Z", Title: "github-secure-session-4"}
groupHeader := groupListHeader(28, sortDefault)
@ -746,7 +766,7 @@ func TestChatMembersDefaultToNewestFirstLikeGitcrawl(t *testing.T) {
}
}
func TestChatMembersScopeSortUsesScopeNotContainer(t *testing.T) {
func TestChatGroupScopeSortUsesScopeNotContainer(t *testing.T) {
m := newModel(Options{
Title: "slacrawl archive",
Layout: LayoutChat,
@ -755,13 +775,12 @@ func TestChatMembersScopeSortUsesScopeNotContainer(t *testing.T) {
Row{Kind: "message", ID: "two", Scope: "a-workspace", Container: "general", Title: "two"}.ItemForLayout(LayoutChat),
},
})
m.setMemberSortMode(sortScope)
members := m.currentGroupMembers()
if len(members) != 2 {
t.Fatalf("members = %#v", members)
m.setSortMode(sortScope)
if len(m.groups) != 2 {
t.Fatalf("groups = %#v", m.groups)
}
if got := m.items[members[0]].ID; got != "two" {
t.Fatalf("scope-sorted first member = %q, want a-workspace row", got)
if got := m.groups[0].Scope; got != "a-workspace" {
t.Fatalf("scope-sorted first group = %q, want a-workspace", got)
}
}
@ -1607,6 +1626,16 @@ func TestDocumentExplorerGroupsParentsAndListsPages(t *testing.T) {
}
}
func TestDocumentGroupsDoNotMergeSameParentAcrossWorkspaces(t *testing.T) {
m := newModel(Options{Layout: LayoutDocument, Items: []Item{
Row{Kind: "page", Scope: "workspace-a", ParentID: "Client Dashboards", Title: "one", UpdatedAt: "2026-05-02T12:00:00Z"}.ItemForLayout(LayoutDocument),
Row{Kind: "page", Scope: "workspace-b", ParentID: "Client Dashboards", Title: "two", UpdatedAt: "2026-05-02T13:00:00Z"}.ItemForLayout(LayoutDocument),
}})
if len(m.groups) != 2 {
t.Fatalf("same parent names in different workspaces should stay separate: %#v", m.groups)
}
}
func TestDocumentContextColumnsAvoidEmptyChatAuthorSlot(t *testing.T) {
m := newModel(Options{
Title: "notcrawl archive",