From f290d7efad222397b77e454bbb19980be9dcc426 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:47:00 -0300 Subject: [PATCH] feat(app): reorder crews by dragging or from the menu Drag a crew's name in the sidebar, or use Move up / Move down in its menu. The order is a look of this computer, kept in localStorage; the crews page follows it. Sidebar conversation lines moved to their own file. --- app/src/features/crews/CrewsOverview.tsx | 3 +- app/src/features/crews/Sidebar.tsx | 177 ++++++------------ .../features/crews/SidebarConversation.tsx | 111 +++++++++++ app/src/features/crews/crewMenu.test.tsx | 1 + app/src/features/crews/crewOrder.test.tsx | 78 ++++++++ app/src/features/crews/crewOrder.ts | 85 +++++++++ app/src/features/crews/useCrewDrag.ts | 99 ++++++++++ app/src/i18n/en/crews.ts | 2 + app/src/i18n/es/crews.ts | 2 + app/src/i18n/pt-BR/crews.ts | 2 + docs/spec.md | 2 +- 11 files changed, 445 insertions(+), 117 deletions(-) create mode 100644 app/src/features/crews/SidebarConversation.tsx create mode 100644 app/src/features/crews/crewOrder.test.tsx create mode 100644 app/src/features/crews/crewOrder.ts create mode 100644 app/src/features/crews/useCrewDrag.ts diff --git a/app/src/features/crews/CrewsOverview.tsx b/app/src/features/crews/CrewsOverview.tsx index fe66f7fc..4b9da228 100644 --- a/app/src/features/crews/CrewsOverview.tsx +++ b/app/src/features/crews/CrewsOverview.tsx @@ -10,6 +10,7 @@ import { CountBadge } from "../../ui/Badge"; import { Button } from "../../ui/Button"; import { BotAvatar, moodOf } from "../bots/BotAvatar"; import { CrewDialog } from "./CrewDialog"; +import { orderedCrews, useCrewOrder } from "./crewOrder"; /** Faces shown on a card; the rest are counted. */ const FACES = 6; @@ -18,7 +19,7 @@ const FACES = 6; export function CrewsOverview() { const t = useT(); const words = t.crews.overview; - const crews = useApp(useShallow(crewList)); + const crews = orderedCrews(useApp(useShallow(crewList)), useCrewOrder()); const botCount = useApp((state) => Object.keys(state.bots).length); const [creating, setCreating] = useState(false); return ( diff --git a/app/src/features/crews/Sidebar.tsx b/app/src/features/crews/Sidebar.tsx index feeab6b3..c5a8535e 100644 --- a/app/src/features/crews/Sidebar.tsx +++ b/app/src/features/crews/Sidebar.tsx @@ -1,35 +1,42 @@ -import { ChevronDown, ChevronsDownUp, ChevronsUpDown, Pause, Plus } from "lucide-react"; -import { memo, useCallback, useState } from "react"; +import { + ArrowDown, + ArrowUp, + ChevronDown, + ChevronsDownUp, + ChevronsUpDown, + Pause, + Plus, +} from "lucide-react"; +import { useCallback, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { useShallow } from "zustand/react/shallow"; -import { type Messages, useT } from "../../i18n"; -import { when } from "../../lib/format"; -import { plainText } from "../../lib/plainText"; -import type { Activity, Bot, Crew } from "../../lib/protocol.gen"; -import { activityOf, crewList } from "../../store/app"; +import { useT } from "../../i18n"; +import type { Crew, CrewId } from "../../lib/protocol.gen"; +import { crewList } from "../../store/app"; import { useApp } from "../../store/context"; -import { isUnread, unreadIn } from "../../store/seen"; +import { unreadIn } from "../../store/seen"; import { CountBadge } from "../../ui/Badge"; import { Button } from "../../ui/Button"; import { ContextMenu, menuPoint, type Point } from "../../ui/ContextMenu"; -import { APP_OPENED } from "../../ui/motion"; -import { BotStateBadge, stateView } from "../bots/BotStateBadge"; -import { useBotActions } from "../bots/botActions"; -import { ChiefBadge, isChief } from "../bots/ChiefBadge"; -import { ListAvatar } from "../bots/ListAvatar"; -import { toolAction, toolTitle } from "../chat/toolNames"; import { CrewDialog } from "./CrewDialog"; import { setAllCollapsed, setCollapsed, useCollapsed, useCollapsedSet } from "./collapsed"; import { useCrewActions } from "./crewActions"; +import { moveBy, orderedCrews, setCrewOrder, useCrewOrder } from "./crewOrder"; +import { Conversation } from "./SidebarConversation"; import { row } from "./SidebarPages"; import { StateFilters } from "./StateFilters"; import { filteredBots, useStateFilter } from "./stateFilter"; +import { useCrewDrag } from "./useCrewDrag"; /** Crews as sections and their bots as conversations (spec 15.1). */ export function Sidebar() { const t = useT(); const words = t.crews.sidebar; - const crews = useApp(useShallow(crewList)); + const order = useCrewOrder(); + const crews = orderedCrews(useApp(useShallow(crewList)), order); + const ids = crews.map((crew) => crew.id); + const list = useRef(null); + const drag = useCrewDrag(ids, list); const overview = useApp((state) => state.selectedCrewId === null && !state.page); const selectCrew = useApp((state) => state.selectCrew); const collapsed = useCollapsedSet(); @@ -66,9 +73,9 @@ export function Sidebar() { /> {crews.length > 0 && } -