refactor(settings): scope the settings project selector to settings

Picking a project in Settings called setActiveProject, which relocates
the chat, the session list, the file tree and the Git surface. Reading
another project's MCP servers or agents moved the user's whole app.

It had to, because the configuration stores resolved the directory
themselves from the active project and held one flat list. Each of them
now takes an explicit directory — omitted still means the active project,
so every caller outside Settings is unchanged — and keys loaded data by
directory next to a flat mirror of the active project. Chat, autocompletes
and pickers keep reading that mirror; a load for another directory writes
only the map. A failed load restores that directory's previous list.

Settings resolves its own directory through useSettingsDirectory, backed
by a session-local settingsProjectPath that follows the active project
until the user picks something else.
This commit is contained in:
Bohdan Triapitsyn
2026-08-22 20:31:31 +03:00
parent ac0b17c9fd
commit 0079347edc
25 changed files with 703 additions and 250 deletions
+32
View File
@@ -198,6 +198,38 @@ These rules are important. Breaking them tends to reintroduce idle CPU churn, st
9. Global session refresh must remain bounded and failure-isolated per directory.
10. Global session cache must not drive live activity indicators or message-loading state.
### Configuration stores and the Settings directory
`useAgentsStore`, `useCommandsStore`, `useSkillsStore`, `useMcpConfigStore` and
the provider half of `useConfigStore` describe **one project's configuration**.
Two surfaces read them at once: the app (chat, autocompletes, pickers), which
wants the active project, and Settings, whose own project selector may point
somewhere else.
Each of them therefore keeps two things:
- a per-directory map (`agentsByDirectory`, `commandsByDirectory`,
`skillsByDirectory`, `serversByDirectory`, `directoryScoped`);
- a flat mirror (`agents`, `commands`, `skills`, `mcpServers`, `providers`) that
tracks the **active** project only.
Every loader and mutation takes an explicit directory; omitting it means the
active project, which is what non-Settings callers pass. A load for another
directory writes the map and leaves the mirror alone, so browsing another
project in Settings cannot change what chat sees. Components select through
`selectAgentsForDirectory` / `selectCommandsForDirectory` /
`selectSkillsForDirectory` / `selectMcpServersForDirectory` /
`selectProvidersForDirectory`, which return stored arrays.
Settings resolves its directory through `useSettingsDirectory`, backed by
`useUIStore.settingsProjectPath`. That selection is Settings-local and not
persisted: it follows the active project until the user picks another one. The
Settings project selector must never call `setActiveProject` — that relocates
the chat, the session list and the file tree.
Failure is still not empty: a failed load restores that directory's previous
list rather than clearing it.
## Selector Rules
Use leaf selectors.