diff --git a/internal/ui/chat/guilds_tree.go b/internal/ui/chat/guilds_tree.go index ebf5ee6..e3b45eb 100644 --- a/internal/ui/chat/guilds_tree.go +++ b/internal/ui/chat/guilds_tree.go @@ -59,7 +59,7 @@ func (gt *guildsTree) resetNodeIndex() { gt.dmRootNode = nil } -func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder) { +func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder, guildsByID map[discord.GuildID]*gateway.GuildCreateEvent) { name := "Folder" if folder.Name != "" { name = folder.Name @@ -72,14 +72,10 @@ func (gt *guildsTree) createFolderNode(folder gateway.GuildFolder) { } gt.GetRoot().AddChild(folderNode) - for _, gID := range folder.GuildIDs { - guild, err := gt.chatView.state.Cabinet.Guild(gID) - if err != nil { - slog.Error("failed to get guild from state", "guild_id", gID, "err", err) - continue + for _, guildID := range folder.GuildIDs { + if guildEvent, ok := guildsByID[guildID]; ok { + gt.createGuildNode(folderNode, guildEvent.Guild) } - - gt.createGuildNode(folderNode, *guild) } } diff --git a/internal/ui/chat/state.go b/internal/ui/chat/state.go index b31f30f..db6337e 100644 --- a/internal/ui/chat/state.go +++ b/internal/ui/chat/state.go @@ -80,34 +80,64 @@ func (v *View) onRaw(event *ws.RawEvent) { ) } -func (v *View) onReady(r *gateway.ReadyEvent) { - dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{}) +func (v *View) onReady(event *gateway.ReadyEvent) { // Rebuild indexes from scratch so reconnects and account switches do not // retain stale pointers to detached tree nodes. v.guildsTree.resetNodeIndex() + + dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{}) v.guildsTree.dmRootNode = dmNode + root := v.guildsTree. GetRoot(). ClearChildren(). AddChild(dmNode) - for _, folder := range r.UserSettings.GuildFolders { - if folder.ID == 0 && len(folder.GuildIDs) == 1 { - guild, err := v.state.Cabinet.Guild(folder.GuildIDs[0]) - if err != nil { - slog.Error( - "failed to get guild from state", - "guild_id", - folder.GuildIDs[0], - "err", - err, - ) - continue - } + // Track guilds already in folders to find orphans (newly joined guilds may not be synced to GuildFolders yet but always appear in GuildPositions) + guildsInFolders := make(map[discord.GuildID]bool) + for _, folder := range event.UserSettings.GuildFolders { + for _, guildID := range folder.GuildIDs { + guildsInFolders[guildID] = true + } + } - v.guildsTree.createGuildNode(root, *guild) + // Build index of all available guilds. + guildsByID := make(map[discord.GuildID]*gateway.GuildCreateEvent, len(event.Guilds)) + for index := range event.Guilds { + guildsByID[event.Guilds[index].ID] = &event.Guilds[index] + } + + // Use GuildPositions for ordering (it's the canonical order). + // Guilds not in any folder are "orphans" - add them directly to root. + positions := event.UserSettings.GuildPositions + // Fallback: GuildPositions shouldn't be nil but handle gracefully + if len(positions) == 0 { + positions = make([]discord.GuildID, 0, len(event.Guilds)) + for _, guildEvent := range event.Guilds { + positions = append(positions, guildEvent.ID) + } + } + + for _, guildID := range positions { + // Already handled in folder processing below + if guildsInFolders[guildID] { + continue + } + + // Orphan guild - add directly to root in order + if guildEvent, ok := guildsByID[guildID]; ok { + v.guildsTree.createGuildNode(root, guildEvent.Guild) + } + } + + // Process folders (real folders and single-guild "folders") + for _, folder := range event.UserSettings.GuildFolders { + if folder.ID == 0 && len(folder.GuildIDs) == 1 { + if guild, ok := guildsByID[folder.GuildIDs[0]]; ok { + v.guildsTree.createGuildNode(root, guild.Guild) + } } else { - v.guildsTree.createFolderNode(folder) + v.guildsTree.createFolderNode(folder, guildsByID) } }