diff --git a/CHANGELOG.md b/CHANGELOG.md index 329e7457..e3ca5fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Highlights:** Personal channel order follows your account across browsers and devices. - Personal sidebar channel order now follows your account across browsers and devices, with the existing local cache retained for offline use. Thanks @isaiahknight-va. +- Preserve saved sidebar channel order, including explicit clears, in SQLite and PostgreSQL JSON exports. - Signed and notarized macOS server archives with the Foundation Developer ID, alongside the desktop installers, and verify their notarization tickets on a clean runner before publication. - Allow macOS release automation to select its credential keychain explicitly on headless hosts. - Updated SQLite, virtual scrolling, Wrangler, and Oxc tooling while preserving runtime minimums and the 48-hour dependency release-age gate. diff --git a/apps/api/internal/store/postgres/export.go b/apps/api/internal/store/postgres/export.go index 7bd5b534..8fecff03 100644 --- a/apps/api/internal/store/postgres/export.go +++ b/apps/api/internal/store/postgres/export.go @@ -22,6 +22,7 @@ func (s *Store) ExportJSON(ctx context.Context, writer io.Writer) error { out := map[string]any{} tables := []string{ "users", "user_notification_settings", "channel_notification_settings", "identities", "workspaces", "workspace_members", "channels", + "user_sidebar_channel_order", "messages", "pinned_messages", "thread_state", "reactions", "events", "event_recipients", "uploads", "channel_reads", "direct_reads", "message_attachments", "direct_conversations", "direct_conversation_members", diff --git a/apps/api/internal/store/sqlite/export.go b/apps/api/internal/store/sqlite/export.go index 9680222a..0fb16473 100644 --- a/apps/api/internal/store/sqlite/export.go +++ b/apps/api/internal/store/sqlite/export.go @@ -22,6 +22,7 @@ func (s *Store) ExportJSON(ctx context.Context, writer io.Writer) error { out := map[string]any{} tables := []string{ "users", "user_notification_settings", "channel_notification_settings", "identities", "workspaces", "workspace_members", "channels", + "user_sidebar_channel_order", "messages", "pinned_messages", "thread_state", "reactions", "events", "event_recipients", "uploads", "channel_reads", "direct_reads", "message_attachments", "direct_conversations", "direct_conversation_members", diff --git a/apps/api/internal/store/storetest/sidebar.go b/apps/api/internal/store/storetest/sidebar.go index 23cc059d..b35e0927 100644 --- a/apps/api/internal/store/storetest/sidebar.go +++ b/apps/api/internal/store/storetest/sidebar.go @@ -1,9 +1,12 @@ package storetest import ( + "bytes" "context" + "encoding/json" "errors" "fmt" + "io" "reflect" "testing" "time" @@ -12,7 +15,10 @@ import ( ) // SidebarPreferences exercises the same persistence contract on both databases. -func SidebarPreferences(t *testing.T, st store.Store) { +func SidebarPreferences(t *testing.T, st interface { + store.Store + ExportJSON(context.Context, io.Writer) error +}) { t.Helper() ctx := context.Background() suffix := fmt.Sprint(time.Now().UnixNano()) @@ -81,4 +87,32 @@ func SidebarPreferences(t *testing.T, st store.Store) { t.Fatal("accepted oversized order") } check() + var output bytes.Buffer + if err := st.ExportJSON(ctx, &output); err != nil { + t.Fatal(err) + } + var snapshot struct { + Orders []struct { + UserID string `json:"user_id"` + WorkspaceID string `json:"workspace_id"` + ChannelIDs string `json:"channel_ids"` + } `json:"user_sidebar_channel_order"` + } + if err := json.Unmarshal(output.Bytes(), &snapshot); err != nil { + t.Fatal(err) + } + exported := map[string][]string{} + for _, row := range snapshot.Orders { + if row.UserID != user.ID { + continue + } + var order []string + if err := json.Unmarshal([]byte(row.ChannelIDs), &order); err != nil { + t.Fatal(err) + } + exported[row.WorkspaceID] = order + } + if !reflect.DeepEqual(exported, want) { + t.Fatalf("exported sidebar preferences: %v; want: %v", exported, want) + } } diff --git a/docs/features/profiles.md b/docs/features/profiles.md index 83355708..994e848e 100644 --- a/docs/features/profiles.md +++ b/docs/features/profiles.md @@ -100,3 +100,6 @@ dropped and duplicates keep their first position. Up to 100 workspaces and 500 channel IDs per workspace can be saved per request. Larger local orders retain their tail on the browser where they were arranged. Appearance and sidebar preferences update independently. + +SQLite and PostgreSQL JSON exports include the saved per-workspace channel order, +including explicit clears, in `user_sidebar_channel_order`.