feat(settings): present desktop Settings as a full-app modal overlay (#4002)

This commit is contained in:
Cyrus Gray
2026-06-23 21:46:48 +05:30
committed by GitHub
parent 3c98c98158
commit 3a2b38d0c3
40 changed files with 871 additions and 304 deletions
@@ -1,9 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useT } from '../../lib/i18n/I18nContext';
import type { SubconsciousMode } from '../../utils/tauriCommands/heartbeat';
import type { SubconsciousStatus } from '../../utils/tauriCommands/subconscious';
import { settingsNavState } from '../settings/modal/settingsOverlay';
interface ModeOption {
id: SubconsciousMode;
@@ -67,6 +68,7 @@ export default function IntelligenceSubconsciousTab({
}: IntelligenceSubconsciousTabProps) {
const { t } = useT();
const navigate = useNavigate();
const location = useLocation();
const providerUnavailable = status?.provider_available === false;
const providerUnavailableReason = providerUnavailable
? (status?.provider_unavailable_reason ?? t('subconscious.providerUnavailableTitle'))
@@ -238,7 +240,7 @@ export default function IntelligenceSubconsciousTab({
</div>
<button
type="button"
onClick={() => navigate('/settings/llm')}
onClick={() => navigate('/settings/llm', settingsNavState(location))}
className="flex-shrink-0 rounded-md bg-amber-600 px-2.5 py-1.5 text-xs font-medium text-white hover:bg-amber-700 transition-colors">
{t('subconscious.providerSettings')}
</button>
@@ -24,7 +24,7 @@ import {
LuSparkles,
LuX,
} from 'react-icons/lu';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { useT } from '../../lib/i18n/I18nContext';
import { TaskKanbanBoard } from '../../pages/conversations/components/TaskKanbanBoard';
@@ -48,6 +48,7 @@ import {
import type { ThreadMessage } from '../../types/thread';
import type { TaskBoard, TaskBoardCard, TaskBoardCardStatus } from '../../types/turnState';
import { chatThreadPath } from '../../utils/chatRoutes';
import { settingsNavState } from '../settings/modal/settingsOverlay';
import { UserTaskComposer } from './UserTaskComposer';
const log = debug('intelligence:tasks');
@@ -779,6 +780,7 @@ function TaskSourceTaskList({
}) {
const { t } = useT();
const navigate = useNavigate();
const location = useLocation();
const sortedCards = useMemo(
() => [...board.cards].sort((a, b) => a.order - b.order),
[board.cards]
@@ -797,7 +799,7 @@ function TaskSourceTaskList({
</div>
<button
type="button"
onClick={() => navigate('/settings/integrations')}
onClick={() => navigate('/settings/integrations', settingsNavState(location))}
className="text-xs font-medium text-ocean-600 hover:text-ocean-700 dark:text-ocean-300 dark:hover:text-ocean-200">
{t('conversations.taskKanban.sources.manage')}
</button>
@@ -9,7 +9,16 @@ import IntelligenceSubconsciousTab from '../IntelligenceSubconsciousTab';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', () => ({ useNavigate: () => mockNavigate }));
vi.mock('react-router-dom', () => ({
useNavigate: () => mockNavigate,
useLocation: () => ({
pathname: '/intelligence',
search: '',
hash: '',
state: null,
key: 'test',
}),
}));
function baseProps(): ComponentProps<typeof IntelligenceSubconsciousTab> {
return {
@@ -67,7 +67,17 @@ vi.mock('../../../store/hooks', () => ({
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
return { ...actual, useNavigate: () => hoisted.navigate };
return {
...actual,
useNavigate: () => hoisted.navigate,
useLocation: () => ({
pathname: '/intelligence',
search: '',
hash: '',
state: null,
key: 'test',
}),
};
});
// Stub the composer so we can drive its `onCreated` callback without
@@ -276,7 +286,7 @@ describe('IntelligenceTasksTab', () => {
// "Manage sources" jumps to the merged Integrations settings page
// (task-sources was folded into /settings/integrations).
fireEvent.click(screen.getByText('Manage sources'));
expect(hoisted.navigate).toHaveBeenCalledWith('/settings/integrations');
expect(hoisted.navigate).toHaveBeenCalledWith('/settings/integrations', expect.anything());
});
test('refines a source task and approves it into the personal agent board', async () => {