refactor(desktop): move project scope into a dependency-light store module
Profile and gateway-switch stores need to reset the scope synchronously; importing store/projects from them would close an import cycle.
This commit is contained in:
@@ -10,7 +10,8 @@ import { registry } from '@/contrib/registry'
|
||||
import { $connectionsRegistry } from '@/store/connection-registry-state'
|
||||
import { $sidebarMessagingOpenIds, setSidebarAgentsGrouped, setSidebarGrouping } from '@/store/layout'
|
||||
import { $activeGatewayProfile, $profiles, setShowAllProfiles } from '@/store/profile'
|
||||
import { $projectScope, $projectTree, ALL_PROJECTS } from '@/store/projects'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import { $projectTree } from '@/store/projects'
|
||||
import {
|
||||
$currentCwd,
|
||||
$messagingSessions,
|
||||
|
||||
@@ -86,18 +86,16 @@ import {
|
||||
sidebarProfileForScope
|
||||
} from '@/store/profile'
|
||||
import { $profileRailVisible } from '@/store/profile-rail-prefs'
|
||||
import { $projectScope, ALL_PROJECTS, exitProjectScope } from '@/store/project-scope'
|
||||
import {
|
||||
$activeProjectId,
|
||||
$newProjectDropPlacement,
|
||||
$projectOwnerBySessionId,
|
||||
$projects,
|
||||
$projectScope,
|
||||
$projectTree,
|
||||
$projectTreeLoading,
|
||||
$reposScanning,
|
||||
ALL_PROJECTS,
|
||||
enterProject,
|
||||
exitProjectScope,
|
||||
followEnteredProjectCwd,
|
||||
openProjectCreate,
|
||||
refreshProjects,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ensureGatewayProfile,
|
||||
resolveNewChatOwnerRoute
|
||||
} from '@/store/profile'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/projects'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import {
|
||||
$activeSessionId,
|
||||
$sessions,
|
||||
|
||||
@@ -33,7 +33,8 @@ import {
|
||||
ensureGatewayAgent,
|
||||
ensureGatewayProfile
|
||||
} from '@/store/profile'
|
||||
import { $projectScope, $projectTree, ALL_PROJECTS } from '@/store/projects'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import { $projectTree } from '@/store/projects'
|
||||
import {
|
||||
$activeSessionId,
|
||||
$activeSessionStoredIdRotation,
|
||||
|
||||
@@ -55,7 +55,8 @@ import {
|
||||
normalizeProfileKey,
|
||||
resolveNewChatOwnerRoute
|
||||
} from '@/store/profile'
|
||||
import { $projectScope, resolveNewSessionCwd } from '@/store/projects'
|
||||
import { $projectScope } from '@/store/project-scope'
|
||||
import { resolveNewSessionCwd } from '@/store/projects'
|
||||
import { receiveApprovalRequest, replayPendingApproval } from '@/store/prompts'
|
||||
import { clearStoredTranscriptReadOnly, markStoredTranscriptReadOnly } from '@/store/read-only-transcript'
|
||||
import {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ensureGatewayProfile,
|
||||
pinLegacyNewChatProfile
|
||||
} from '@/store/profile'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/projects'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import {
|
||||
isPeerInstanceWindow,
|
||||
isProfilePinnedWindow,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { $activeGatewayProfile, $newChatProfile } from '@/store/profile'
|
||||
import { $projectScope, $projectTree, ALL_PROJECTS } from '@/store/projects'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import { $projectTree } from '@/store/projects'
|
||||
import {
|
||||
$currentBranch,
|
||||
$currentCwd,
|
||||
|
||||
@@ -3,14 +3,8 @@ import { atom, computed, type ReadableAtom } from 'nanostores'
|
||||
import type { HermesGitWorktree, HermesRepoStatus } from '@/global'
|
||||
import { desktopGit } from '@/lib/desktop-git'
|
||||
|
||||
import {
|
||||
$projectScope,
|
||||
$projectTree,
|
||||
$worktreeDialog,
|
||||
$worktreeRefreshToken,
|
||||
ALL_PROJECTS,
|
||||
projectRootCwd
|
||||
} from './projects'
|
||||
import { $projectScope, ALL_PROJECTS } from './project-scope'
|
||||
import { $projectTree, $worktreeDialog, $worktreeRefreshToken, projectRootCwd } from './projects'
|
||||
import {
|
||||
$busy,
|
||||
$currentCwd,
|
||||
|
||||
26
apps/desktop/src/store/project-scope.ts
Normal file
26
apps/desktop/src/store/project-scope.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { persistentAtom } from '@/lib/persisted'
|
||||
|
||||
// ── Project scope (the "you're inside a project" view, mirroring profile scope)─
|
||||
// The sidebar's grouped view is a project switcher: ALL_PROJECTS shows the
|
||||
// project overview (a list you drill into), and a concrete id means you've
|
||||
// "entered" that project so only its worktrees/branches/sessions show. This is
|
||||
// pure view state (localStorage), distinct from the durable active-project
|
||||
// pointer in projects.db — though entering a project also makes it active so new
|
||||
// chats land there, exactly as selecting a profile does.
|
||||
//
|
||||
// Project ids belong to ONE backend's projects.db, so switching profile or
|
||||
// connection must leave the scope (store/profile, store/gateway-switch). It lives
|
||||
// in this dependency-light module so those stores can reset it synchronously,
|
||||
// before the fresh draft resolves its cwd from it.
|
||||
export const ALL_PROJECTS = '__all_projects__'
|
||||
|
||||
const PROJECT_SCOPE_KEY = 'hermes.desktop.projectScope'
|
||||
|
||||
export const $projectScope = persistentAtom<string>(PROJECT_SCOPE_KEY, ALL_PROJECTS, {
|
||||
decode: raw => raw || ALL_PROJECTS,
|
||||
encode: value => value || ALL_PROJECTS
|
||||
})
|
||||
|
||||
export function exitProjectScope(): void {
|
||||
$projectScope.set(ALL_PROJECTS)
|
||||
}
|
||||
@@ -7,18 +7,16 @@ import { $activeGatewayProfile, $profileScope, ALL_PROFILES, setShowAllProfiles
|
||||
import { $currentCwd, $selectedStoredSessionId, $sessions, applyConfiguredDefaultProjectDir } from '@/store/session'
|
||||
import type { ProjectInfo } from '@/types/hermes'
|
||||
|
||||
import { $projectScope, ALL_PROJECTS, exitProjectScope } from './project-scope'
|
||||
import {
|
||||
$activeProjectId,
|
||||
$projects,
|
||||
$projectScope,
|
||||
$projectsRpcAvailable,
|
||||
$projectTree,
|
||||
addProjectFolder,
|
||||
ALL_PROJECTS,
|
||||
createProject,
|
||||
deleteProject,
|
||||
enterProject,
|
||||
exitProjectScope,
|
||||
fetchProjectSessions,
|
||||
openProjectCreate,
|
||||
pickProjectFolder,
|
||||
|
||||
@@ -15,7 +15,6 @@ import { desktopDefaultCwd, isDesktopFsRemoteMode, selectDesktopPaths, writeDesk
|
||||
import { desktopGit } from '@/lib/desktop-git'
|
||||
import { isMissingRestEndpoint, isMissingRpcMethod } from '@/lib/gateway-rpc'
|
||||
import { isUnderPath } from '@/lib/path-compare'
|
||||
import { persistentAtom } from '@/lib/persisted'
|
||||
import { revealFile } from '@/store/file-actions'
|
||||
import { $gateway, activeGateway, ensureActiveGatewayOpen } from '@/store/gateway'
|
||||
import { $sidebarShowAllSessions, setSidebarAgentsGrouped } from '@/store/layout'
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
normalizeProfileKey,
|
||||
requestFreshSession
|
||||
} from '@/store/profile'
|
||||
import { $projectScope, ALL_PROJECTS } from '@/store/project-scope'
|
||||
import {
|
||||
$currentCwd,
|
||||
$selectedStoredSessionId,
|
||||
@@ -80,22 +80,6 @@ function projectsStaleBackendError(): Error {
|
||||
// True while the disk scan is in flight (drives the "finding repos" hint).
|
||||
export const $reposScanning = atom(false)
|
||||
|
||||
// ── Project scope (the "you're inside a project" view, mirroring profile scope)─
|
||||
// The sidebar's grouped view is a project switcher: ALL_PROJECTS shows the
|
||||
// project overview (a list you drill into), and a concrete id means you've
|
||||
// "entered" that project so only its worktrees/branches/sessions show. This is
|
||||
// pure view state (localStorage), distinct from the durable active-project
|
||||
// pointer in projects.db — though entering a project also makes it active so new
|
||||
// chats land there, exactly as selecting a profile does.
|
||||
export const ALL_PROJECTS = '__all_projects__'
|
||||
|
||||
const PROJECT_SCOPE_KEY = 'hermes.desktop.projectScope'
|
||||
|
||||
export const $projectScope = persistentAtom<string>(PROJECT_SCOPE_KEY, ALL_PROJECTS, {
|
||||
decode: raw => raw || ALL_PROJECTS,
|
||||
encode: value => value || ALL_PROJECTS
|
||||
})
|
||||
|
||||
// Enter a project: scope the sidebar to it and make it the active project
|
||||
// (best-effort — the durable pointer is nice-to-have, the view scope is the
|
||||
// point). Never opens a session.
|
||||
@@ -110,10 +94,6 @@ export function enterProject(id: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function exitProjectScope(): void {
|
||||
$projectScope.set(ALL_PROJECTS)
|
||||
}
|
||||
|
||||
// A project's working root: its primary folder, else the first repo that has
|
||||
// one. Empty for the path-less Home bucket. (The sidebar's `projectTreeCwd` is
|
||||
// the same rule over the same tree — this is the store-side copy so the store
|
||||
|
||||
Reference in New Issue
Block a user