feat(sidebar): add single-project display mode
This commit is contained in:
@@ -88,6 +88,8 @@ Project and UI settings use successful settings synchronization as authority. Om
|
||||
|
||||
Project ordering defaults to manual. Session display persistence v3 migrates the previously shipped `recent` project order to `manual` while preserving every other explicit sort mode.
|
||||
|
||||
Session display persistence keeps a hydrated local cache for the independent all-projects/single-project mode, session grouping, project sort, and Recent preference; successful server settings snapshots are authoritative and the UI seeds missing server fields once from that cache for upgrades. The last confirmed or manually selected project and sticky-header preference stay local to the device. Draft target changes do not write the picker selection; materialized session navigation updates it from the resolved project directory.
|
||||
|
||||
Session folders persist in runtime-specific v2 browser keys without silently evicting older runtime namespaces. Runtime switch, page hide, app freeze, and unload synchronously flush the pending browser snapshot before lifecycle suspension or namespace replacement. A runtime switch then cancels stale old-runtime disk work and starts generation-owned disk hydration. Missing or malformed server files are not authoritative empty snapshots; disk data may replace browser state only when it carries a real revision and no newer local folder mutation occurred. Server writes are serialized and reject non-newer revisions so delayed or duplicate requests cannot overwrite the current state. File-search cache and in-flight keys include runtime plus directory and are cleared on endpoint reset.
|
||||
|
||||
Persisted session todos use a bounded composite key of runtime, normalized directory, and session ID. Ambiguous legacy todo entries are discarded rather than claimed by whichever runtime starts first. Authoritative deletion uses an explicit runtime identity, and session-folder deletion scans every scope in the active runtime so archived assignments cannot survive after their session is gone.
|
||||
|
||||
@@ -32,3 +32,26 @@ describe('useSessionDisplayStore project sorting', () => {
|
||||
expect(migrated.showArchivedSessions).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSessionDisplayStore project display', () => {
|
||||
test('defaults to showing all projects without a selected single project', () => {
|
||||
expect(useSessionDisplayStore.getState().projectDisplayMode).toBe('all');
|
||||
expect(useSessionDisplayStore.getState().singleProjectId).toBeNull();
|
||||
});
|
||||
|
||||
test('stores the single-project mode independently from session grouping', () => {
|
||||
useSessionDisplayStore.getState().setProjectDisplayMode('single');
|
||||
useSessionDisplayStore.getState().setSingleProjectId('project-alpha');
|
||||
useSessionDisplayStore.getState().setSessionGroupingMode('flat');
|
||||
|
||||
expect(useSessionDisplayStore.getState().projectDisplayMode).toBe('single');
|
||||
expect(useSessionDisplayStore.getState().singleProjectId).toBe('project-alpha');
|
||||
expect(useSessionDisplayStore.getState().sessionGroupingMode).toBe('flat');
|
||||
|
||||
useSessionDisplayStore.setState({
|
||||
projectDisplayMode: 'all',
|
||||
singleProjectId: null,
|
||||
sessionGroupingMode: 'by-worktree',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,13 @@ type ProjectSortOrder = 'manual' | 'a-z' | 'z-a' | 'date-added' | 'recent';
|
||||
// 'by-worktree' keeps per-worktree sub-headers inside each project zone
|
||||
// (parallel-work overview); 'flat' merges everything into one recency list.
|
||||
type SessionGroupingMode = 'by-worktree' | 'flat';
|
||||
type ProjectDisplayMode = 'all' | 'single';
|
||||
|
||||
type SessionDisplayStore = {
|
||||
projectDisplayMode: ProjectDisplayMode;
|
||||
singleProjectId: string | null;
|
||||
setProjectDisplayMode: (mode: ProjectDisplayMode) => void;
|
||||
setSingleProjectId: (projectId: string) => void;
|
||||
sessionGroupingMode: SessionGroupingMode;
|
||||
setSessionGroupingMode: (mode: SessionGroupingMode) => void;
|
||||
/** Project/recent zone headers stick to the top while their zone scrolls. */
|
||||
@@ -50,6 +55,10 @@ export const migrateSessionDisplayState = (
|
||||
export const useSessionDisplayStore = create<SessionDisplayStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
projectDisplayMode: 'all',
|
||||
singleProjectId: null,
|
||||
setProjectDisplayMode: (mode) => set({ projectDisplayMode: mode }),
|
||||
setSingleProjectId: (projectId) => set({ singleProjectId: projectId }),
|
||||
sessionGroupingMode: 'by-worktree',
|
||||
setSessionGroupingMode: (mode) => set({ sessionGroupingMode: mode }),
|
||||
stickyZoneHeaders: true,
|
||||
@@ -68,13 +77,14 @@ export const useSessionDisplayStore = create<SessionDisplayStore>()(
|
||||
}),
|
||||
{
|
||||
name: 'session-display-mode',
|
||||
version: 4,
|
||||
version: 5,
|
||||
// v1→v2 adds projectSortOrder using the canonical manual ordering.
|
||||
// v2→v3 replaces the previously shipped recent default with manual.
|
||||
// v3→v4 removes displayMode (single sidebar row layout).
|
||||
// v4→v5 adds the independent all-projects/single-project view mode.
|
||||
migrate: migrateSessionDisplayState,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
export type { ProjectSortOrder };
|
||||
export type { ProjectDisplayMode, ProjectSortOrder };
|
||||
|
||||
Reference in New Issue
Block a user