feat: space sidebar drag-and-drop reordering with folder system

Add user_space_layout table and PUT /api/users/@me/space-layout endpoint
for persisting per-user sidebar ordering. Spaces can be freely reordered
via drag-and-drop, folders created by dragging one space onto another,
and folders auto-dissolve when they have fewer than 2 members. Includes
folder context menu (rename, color, ungroup), collapsed folder mini-grid
icons, multi-tab sync via WebSocket, and localStorage collapse state.
Removes the rigid native/federated split — federated spaces now intermix
freely while keeping their globe badge.
This commit is contained in:
Jannis Braun
2026-03-12 03:38:22 +01:00
parent 39174e5454
commit dbd964d40c
12 changed files with 1006 additions and 93 deletions
+11
View File
@@ -36,6 +36,8 @@ import type {
ExploreSpace,
JoinRequest,
Role,
SpaceLayoutItem,
SpaceFolder,
} from '@backspace/shared';
export class RateLimitError extends Error {
@@ -64,6 +66,10 @@ export class BackspaceApiClient {
getMutuals: (id: string, homeUserId?: string) => Promise<{ mutualFriends: User[]; mutualSpaces: { id: string; name: string; icon: string | null; avatarColor: string | null }[] }>;
};
readonly spaceLayout: {
update: (data: { items: SpaceLayoutItem[]; folders: Record<string, { name: string | null; color: string | null; spaceIds: string[] }> }) => Promise<{ items: SpaceLayoutItem[]; folders: SpaceFolder[] }>;
};
readonly spaces: {
list: () => Promise<Space[]>;
get: (id: string) => Promise<SpaceWithChannelsAndMembers>;
@@ -270,6 +276,11 @@ export class BackspaceApiClient {
},
};
this.spaceLayout = {
update: (data) =>
request<{ items: SpaceLayoutItem[]; folders: SpaceFolder[] }>('PUT', '/users/@me/space-layout', data),
};
this.spaces = {
list: () => request<Space[]>('GET', '/spaces'),
get: (id: string) => request<SpaceWithChannelsAndMembers>('GET', `/spaces/${id}`),