Files
openchamber/packages/ui/src/stores/useWorktreeOrderStore.ts
T
Bohdan Triapitsyn 79d68ba583 feat: add mobile project editor with worktree reorder and delete
Replace the up/down arrows in the mobile project reorder list (drag already
covers reordering) with an edit button that opens a dedicated project editor
surface: rename, pick icon and color, and discover a favicon (no upload).

The editor lists the project's worktrees with drag-to-reorder (persisted per
project, like project order) and a delete button that opens a mobile
confirmation built on the shared worktree primitives — archiving attached
sessions and optionally removing the local/remote branch.
2026-06-02 16:10:20 +03:00

58 lines
2.1 KiB
TypeScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { WorktreeMetadata } from '@/types/worktree';
/**
* Persisted display order for worktrees within a project, mirroring how
* projects persist their order (the array position IS the order). Keyed by
* project id, the value is an ordered list of normalized worktree paths.
*
* Worktrees come from git and are otherwise listed alphabetically; this store
* lets the user reorder them (e.g. in the mobile project editor) and have that
* order stick across restarts.
*/
type WorktreeOrderStore = {
orderByProject: Record<string, string[]>;
setWorktreeOrder: (projectId: string, orderedPaths: string[]) => void;
};
export const useWorktreeOrderStore = create<WorktreeOrderStore>()(
persist(
(set) => ({
orderByProject: {},
setWorktreeOrder: (projectId, orderedPaths) =>
set((state) => ({ orderByProject: { ...state.orderByProject, [projectId]: orderedPaths } })),
}),
{
name: 'mobile-worktree-order',
},
),
);
const normalizeWorktreePath = (value: string): string =>
value.replace(/\\/g, '/').replace(/\/+$/, '');
/**
* Stable-sort worktrees by a stored order of paths. Worktrees not present in
* the stored order keep their incoming (alphabetical) order, appended after
* the known ones.
*/
export const orderWorktrees = (
orderedPaths: string[] | undefined,
worktrees: WorktreeMetadata[],
): WorktreeMetadata[] => {
if (!orderedPaths || orderedPaths.length === 0) return worktrees;
const rank = new Map(orderedPaths.map((path, index) => [normalizeWorktreePath(path), index] as const));
const rankOf = (worktree: WorktreeMetadata): number =>
rank.get(normalizeWorktreePath(worktree.path)) ?? Number.MAX_SAFE_INTEGER;
return worktrees
.map((worktree, index) => ({ worktree, index }))
.sort((a, b) => {
const byRank = rankOf(a.worktree) - rankOf(b.worktree);
// Preserve incoming order for ties (unknown worktrees / equal ranks).
return byRank !== 0 ? byRank : a.index - b.index;
})
.map((entry) => entry.worktree);
};