Skip to content

Commit cb33a63

Browse files
committed
feat(ui): deep-link auth nudge to the Security section + flash it
"Open Settings" from the install panel now lands on the Security section instead of the top of Settings. A `pendingSettingsSection` store signal is set to 'security' before navigating; SettingsPage scrolls that section into view and briefly flashes a ring around it, then clears the signal. Also tighten the Disable-authentication help text to two lines. Claude-Session: https://claude.ai/code/session_01Baan9JmzR43uxxRUh7CAMF Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent dd0c492 commit cb33a63

6 files changed

Lines changed: 60 additions & 8 deletions

File tree

apps/desktop/src/features/settings/SettingsPage.tsx

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22
import { invoke } from '@tauri-apps/api/core';
33
import {
44
Card,
@@ -33,7 +33,13 @@ import {
3333
AlertCircle,
3434
ShieldOff,
3535
} from 'lucide-react';
36-
import { useAppStore, useTheme, useAnalyticsEnabled } from '@/stores';
36+
import {
37+
useAppStore,
38+
useTheme,
39+
useAnalyticsEnabled,
40+
usePendingSettingsSection,
41+
useSetPendingSettingsSection,
42+
} from '@/stores';
3743
import { UpdateChecker } from './UpdateChecker';
3844
import { useGatewayControl } from '@/features/gateway/useGatewayControl';
3945
import { CONTRIBUTE, openExternal } from '@/lib/contribute';
@@ -60,6 +66,22 @@ export function SettingsPage() {
6066
const { toasts, success, error } = useToast();
6167
const gatewayControl = useGatewayControl();
6268

69+
// Deep-link: when another surface routes here for a specific section, scroll
70+
// it into view and briefly flash it so the user lands on the right control.
71+
const pendingSection = usePendingSettingsSection();
72+
const clearPendingSection = useSetPendingSettingsSection();
73+
const securityRef = useRef<HTMLDivElement>(null);
74+
const [flashSecurity, setFlashSecurity] = useState(false);
75+
76+
useEffect(() => {
77+
if (pendingSection !== 'security' || !securityRef.current) return;
78+
securityRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
79+
setFlashSecurity(true);
80+
clearPendingSection(null);
81+
const t = setTimeout(() => setFlashSecurity(false), 2200);
82+
return () => clearTimeout(t);
83+
}, [pendingSection, clearPendingSection]);
84+
6385
// Startup settings state
6486
const [startupSettings, setStartupSettings] = useState<StartupSettings>({
6587
autoLaunch: false,
@@ -626,6 +648,15 @@ export function SettingsPage() {
626648
</Card>
627649

628650
{/* Security Section */}
651+
<div
652+
ref={securityRef}
653+
id="settings-security"
654+
className={
655+
flashSecurity
656+
? 'rounded-xl ring-2 ring-primary-500 ring-offset-2 ring-offset-[rgb(var(--background))] transition-shadow duration-500'
657+
: 'rounded-xl ring-0 transition-shadow duration-500'
658+
}
659+
>
629660
<Card data-testid="settings-security-section">
630661
<CardHeader>
631662
<CardTitle className="flex items-center gap-2">
@@ -643,10 +674,8 @@ export function SettingsPage() {
643674
<div>
644675
<label className="text-sm font-medium">Disable authentication</label>
645676
<p className="mt-1 text-xs text-[rgb(var(--muted))]">
646-
Let local apps connect to the gateway with no access key — just the URL and a
647-
workspace header. Makes one-click per-workspace setup trivial. The gateway only
648-
listens on localhost, but any app on this machine can then reach it. Leave on
649-
unless you want the simplest setup.
677+
Let local apps connect with no access key — just the URL and a workspace header.
678+
Quickest setup, but any app on this machine can then reach the gateway.
650679
</p>
651680
</div>
652681
</div>
@@ -659,6 +688,7 @@ export function SettingsPage() {
659688
</div>
660689
</CardContent>
661690
</Card>
691+
</div>
662692

663693
{/* Appearance Section */}
664694
<Card>

apps/desktop/src/features/workspaces/WorkspaceInstallPanel.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
22
import { Check, Copy, Download, Loader2, ShieldCheck, ShieldOff, AlertCircle } from 'lucide-react';
33
import { Button } from '@mcpmux/ui';
44
import { getGatewayStatus } from '@/lib/api/gateway';
5-
import { useNavigateTo } from '@/stores';
5+
import { useNavigateTo, useSetPendingSettingsSection } from '@/stores';
66
import {
77
generateWorkspaceConfigSnippet,
88
getGatewayAuthDisabled,
@@ -63,6 +63,7 @@ export function WorkspaceInstallPanel({ workspaceRoot }: { workspaceRoot: string
6363
const [copiedId, setCopiedId] = useState<string | null>(null);
6464
const [error, setError] = useState<string | null>(null);
6565
const navigateTo = useNavigateTo();
66+
const setPendingSettingsSection = useSetPendingSettingsSection();
6667

6768
useEffect(() => {
6869
let cancelled = false;
@@ -171,7 +172,10 @@ export function WorkspaceInstallPanel({ workspaceRoot }: { workspaceRoot: string
171172
variant="secondary"
172173
size="sm"
173174
className="mt-2 h-7 text-xs"
174-
onClick={() => navigateTo('settings')}
175+
onClick={() => {
176+
setPendingSettingsSection('security');
177+
navigateTo('settings');
178+
}}
175179
data-testid="workspace-install-open-auth-settings"
176180
>
177181
<ShieldOff className="mr-1.5 h-3 w-3" />

apps/desktop/src/stores/appStore.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const initialState: AppState = {
88
viewSpaceId: null,
99
activeNav: 'home',
1010
pendingClientId: null,
11+
pendingSettingsSection: null,
1112
sidebarCollapsed: false,
1213
theme: 'system',
1314
analyticsEnabled: true,
@@ -78,6 +79,11 @@ export const useAppStore = create<AppStore>()(
7879
state.pendingClientId = id;
7980
}),
8081

82+
setPendingSettingsSection: (section) =>
83+
set((state) => {
84+
state.pendingSettingsSection = section;
85+
}),
86+
8187
// UI
8288
toggleSidebar: () =>
8389
set((state) => {

apps/desktop/src/stores/selectors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ export const useActiveNav = () => useAppStore((state) => state.activeNav);
88
export const useNavigateTo = () => useAppStore((state) => state.navigateTo);
99
export const usePendingClientId = () => useAppStore((state) => state.pendingClientId);
1010
export const useSetPendingClientId = () => useAppStore((state) => state.setPendingClientId);
11+
export const usePendingSettingsSection = () =>
12+
useAppStore((state) => state.pendingSettingsSection);
13+
export const useSetPendingSettingsSection = () =>
14+
useAppStore((state) => state.setPendingSettingsSection);
1115
export const useTheme = () => useAppStore((state) => state.theme);
1216
export const useSidebarCollapsed = () => useAppStore((state) => state.sidebarCollapsed);
1317
export const useAnalyticsEnabled = () => useAppStore((state) => state.analyticsEnabled);

apps/desktop/src/stores/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface AppState {
2626
activeNav: NavItem;
2727
/** Client ID to auto-select when navigating to Clients page */
2828
pendingClientId: string | null;
29+
/** Section to scroll to + flash when navigating to Settings (e.g. 'security'). */
30+
pendingSettingsSection: string | null;
2931

3032
// UI state
3133
sidebarCollapsed: boolean;
@@ -50,6 +52,7 @@ export interface AppActions {
5052
// Navigation
5153
navigateTo: (nav: NavItem) => void;
5254
setPendingClientId: (id: string | null) => void;
55+
setPendingSettingsSection: (section: string | null) => void;
5356

5457
// UI
5558
toggleSidebar: () => void;

tests/ts/components/WorkspaceInstallPanel.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ const {
2121
getAuthMock,
2222
gatewayStatusMock,
2323
navigateMock,
24+
setSectionMock,
2425
} = vi.hoisted(() => ({
2526
listClientsMock: vi.fn(),
2627
installMock: vi.fn(),
2728
snippetMock: vi.fn(),
2829
getAuthMock: vi.fn(),
2930
gatewayStatusMock: vi.fn(),
3031
navigateMock: vi.fn(),
32+
setSectionMock: vi.fn(),
3133
}));
3234

3335
vi.mock('@/lib/api/workspaceInstall', () => ({
@@ -43,6 +45,7 @@ vi.mock('@/lib/api/gateway', () => ({
4345

4446
vi.mock('@/stores', () => ({
4547
useNavigateTo: () => navigateMock,
48+
useSetPendingSettingsSection: () => setSectionMock,
4649
}));
4750

4851
import { WorkspaceInstallPanel } from '@/features/workspaces/WorkspaceInstallPanel';
@@ -65,6 +68,7 @@ describe('WorkspaceInstallPanel', () => {
6568
snippetMock.mockReset();
6669
getAuthMock.mockReset().mockResolvedValue(true);
6770
navigateMock.mockReset();
71+
setSectionMock.mockReset();
6872
gatewayStatusMock
6973
.mockReset()
7074
.mockResolvedValue({ running: true, url: 'http://localhost:45818' });
@@ -134,6 +138,7 @@ describe('WorkspaceInstallPanel', () => {
134138
// flipping it inline.
135139
const openSettings = await screen.findByTestId('workspace-install-open-auth-settings');
136140
await user.click(openSettings);
141+
expect(setSectionMock).toHaveBeenCalledWith('security');
137142
expect(navigateMock).toHaveBeenCalledWith('settings');
138143
});
139144

0 commit comments

Comments
 (0)