Skip to content

Commit b4ba091

Browse files
committed
feat(ui): premium Active FeatureSet + empty-state Clients page onboarding
Three UX fixes driven by first real-app testing. FeatureSets page — Active state is now recognisable * Top-right corner ribbon with a gradient (emerald → green), Zap icon, uppercase "ACTIVE" label, and a soft outer glow. Hard to miss, reads "premium" not "toast notification". * Active cards get a green ring + emerald-tinted background wash + a stronger drop shadow so the whole card feels elevated, not just badged. * "Applied to this Space" caption replaces the footer Set-Active button when a card is Active — users don't have to hunt for what the ribbon means. * New explainer banner above the grid ("One Active FeatureSet per Space…") so the feature is self-documenting. "Set Active" button is now obviously a button * Proper pill with emerald border, hover-fill gradient, Zap icon. No longer competes visually with "Configure". * Loading state (spinner + "Activating…") fires during the Tauri round-trip so a slow backend doesn't feel like a no-op. * Triple event-guards on click (stopPropagation + preventDefault + nativeEvent.stopImmediatePropagation) so no wrapping card onClick can hijack the gesture. onMouseDown is also guarded. * Success toast now says "{name} is now Active" + describes scope so users know what they just did. Clients page — empty state is a guided three-step onboarding * Replaces the "No clients connected" card with a numbered walk-through: 1. Add mcpmux to your IDE (ConnectIDEs grid, embedded) 2. Restart / reconnect the MCP server in that IDE 3. Approve the connection here — "on this page" pill * ConnectIDEs is reused from the Dashboard so users never leave the page to finish the flow. Gateway URL + status are fetched on mount. * When the gateway is stopped, an amber warning box appears inside the card telling users it needs to be running, otherwise the IDE will hang at `initialize` (the exact bug that motivated this fix). * ConnectIDEs header description now reminds users to restart the MCP server in the IDE after add, and that approval shows up in this app. Cleanup * Drop a stale `eslint-disable no-console` directive in MetaToolApprovalDialog that the linter flagged as unused. pnpm typecheck + lint clean; no new warnings introduced. Existing 31 warnings are all pre-existing useEffect dependency suppressions. Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 382c374 commit b4ba091

4 files changed

Lines changed: 231 additions & 52 deletions

File tree

apps/desktop/src/components/ConnectIDEs.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ export function ConnectIDEs({ gatewayUrl, gatewayRunning }: ConnectIDEsProps) {
127127
<div>
128128
<CardTitle>Connect Your IDEs</CardTitle>
129129
<CardDescription>
130-
Add McpMux to your AI clients. Auth happens on first connect.
130+
Add mcpmux to your AI client, then <span className="font-medium">restart / reconnect
131+
the MCP server in that client</span> — an approval prompt appears in this app when it
132+
hits the gateway.
131133
</CardDescription>
132134
</div>
133135
<div className="flex items-center gap-1.5 text-xs text-[rgb(var(--muted))]">

apps/desktop/src/features/clients/ClientsPage.tsx

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,13 @@ import {
2525
Search,
2626
AlertCircle,
2727
Zap,
28+
PlugZap,
29+
RotateCcw,
30+
CheckCircle2,
2831
} from 'lucide-react';
32+
import { ConnectIDEs } from '@/components/ConnectIDEs';
33+
import type { GatewayStatus } from '@/lib/api/gateway';
34+
import { getGatewayStatus } from '@/lib/api/gateway';
2935
import {
3036
Card,
3137
CardContent,
@@ -132,6 +138,15 @@ export default function ClientsPage() {
132138
const [editMode, setEditMode] = useState('follow_active');
133139
const [editLockedSpaceId, setEditLockedSpaceId] = useState('');
134140
const [isSaving, setIsSaving] = useState(false);
141+
142+
// Gateway status for the empty-state ConnectIDEs embed — fetched once on
143+
// mount + refreshed whenever the gateway starts/stops elsewhere in the app.
144+
const [gatewayStatus, setGatewayStatus] = useState<GatewayStatus>({
145+
running: false,
146+
url: null,
147+
active_sessions: 0,
148+
connected_backends: 0,
149+
});
135150

136151
// Feature set grant state
137152
const viewSpace = useViewSpace();
@@ -269,6 +284,9 @@ export default function ClientsPage() {
269284

270285
useEffect(() => {
271286
loadData();
287+
// Prime the gateway status so the empty-state ConnectIDEs can show the
288+
// real mcpmux URL. Silent failure — the empty state tolerates stale data.
289+
getGatewayStatus().then(setGatewayStatus).catch(() => {});
272290
}, []);
273291

274292
// Auto-open a client panel when navigated from "Manage Permissions"
@@ -603,20 +621,114 @@ export default function ClientsPage() {
603621
<Loader2 className="h-8 w-8 animate-spin text-primary-500" />
604622
</div>
605623
) : filteredClients.length === 0 ? (
606-
<Card className="max-w-2xl mx-auto">
607-
<CardContent className="flex flex-col items-center justify-center py-16">
608-
<Laptop className="h-16 w-16 text-[rgb(var(--muted))] mb-4" />
609-
<h3 className="text-lg font-medium mb-2">
610-
{searchQuery ? 'No clients match your search' : 'No clients connected'}
611-
</h3>
612-
<p className="text-sm text-[rgb(var(--muted))] text-center max-w-md">
613-
{searchQuery
614-
? 'Try adjusting your search terms'
615-
: 'Clients like Cursor or VS Code will appear here after connecting via OAuth'
616-
}
617-
</p>
618-
</CardContent>
619-
</Card>
624+
searchQuery ? (
625+
// Narrow empty state: keep the original card + copy for the
626+
// "no search results" case; ConnectIDEs only shows on
627+
// truly-empty (no clients configured at all).
628+
<Card className="max-w-2xl mx-auto">
629+
<CardContent className="flex flex-col items-center justify-center py-16">
630+
<Laptop className="h-16 w-16 text-[rgb(var(--muted))] mb-4" />
631+
<h3 className="text-lg font-medium mb-2">No clients match your search</h3>
632+
<p className="text-sm text-[rgb(var(--muted))] text-center max-w-md">
633+
Try adjusting your search terms.
634+
</p>
635+
</CardContent>
636+
</Card>
637+
) : (
638+
// True empty — nothing has connected yet. Give them everything
639+
// they need to finish the flow: install the IDE config, restart
640+
// the IDE, then come back and approve. Each step is a distinct
641+
// visual block so users don't skim past "and then approve here".
642+
<div className="max-w-4xl mx-auto space-y-6" data-testid="clients-empty-connect">
643+
<Card>
644+
<CardContent className="p-8">
645+
<div className="flex items-start gap-4 mb-2">
646+
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-primary-500 to-primary-600 text-white shadow-[0_6px_16px_-4px_rgb(99_102_241/0.45)] flex-shrink-0">
647+
<PlugZap className="h-6 w-6" />
648+
</div>
649+
<div>
650+
<h2 className="text-xl font-semibold">Connect your first IDE</h2>
651+
<p className="text-sm text-[rgb(var(--muted))] mt-1">
652+
Nothing has connected to mcpmux yet. It only takes three steps:
653+
</p>
654+
</div>
655+
</div>
656+
657+
{/* Three-step flow. Each step is explicit so users don't
658+
get lost between "I added the config" and "why doesn't
659+
my client show up here?". */}
660+
<ol className="mt-6 space-y-3 text-sm">
661+
<li className="flex gap-3 items-start">
662+
<span className="flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 font-semibold text-xs">
663+
1
664+
</span>
665+
<div className="flex-1">
666+
<p className="font-medium">Add mcpmux to your IDE</p>
667+
<p className="text-[rgb(var(--muted))] text-xs mt-0.5">
668+
Pick one from the cards below — we&apos;ll copy the config or deep-link
669+
straight into the IDE.
670+
</p>
671+
</div>
672+
</li>
673+
<li className="flex gap-3 items-start">
674+
<RotateCcw className="h-5 w-5 mt-0.5 text-[rgb(var(--muted))] flex-shrink-0" />
675+
<span className="flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900/40 text-primary-700 dark:text-primary-300 font-semibold text-xs">
676+
2
677+
</span>
678+
<div className="flex-1">
679+
<p className="font-medium">Start or restart the MCP server in that IDE</p>
680+
<p className="text-[rgb(var(--muted))] text-xs mt-0.5">
681+
Some clients connect automatically on restart; others (Cursor, Claude
682+
Code) need you to toggle &quot;mcpmux&quot; on once.
683+
</p>
684+
</div>
685+
</li>
686+
<li className="flex gap-3 items-start">
687+
<CheckCircle2 className="h-5 w-5 mt-0.5 text-[rgb(var(--muted))] flex-shrink-0" />
688+
<span className="flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-700 dark:text-emerald-300 font-semibold text-xs">
689+
3
690+
</span>
691+
<div className="flex-1">
692+
<p className="font-medium">
693+
Approve the connection here
694+
<span className="ml-2 inline-flex items-center px-1.5 py-0.5 rounded-md bg-emerald-500/15 text-emerald-700 dark:text-emerald-300 text-[10px] font-semibold uppercase tracking-wide">
695+
on this page
696+
</span>
697+
</p>
698+
<p className="text-[rgb(var(--muted))] text-xs mt-0.5">
699+
As soon as the IDE hits the gateway, mcpmux will pop an approval
700+
dialog. Pick a Space + FeatureSet pin and the client shows up right
701+
here. Nothing gets routed until you approve.
702+
</p>
703+
</div>
704+
</li>
705+
</ol>
706+
707+
{!gatewayStatus.running && (
708+
<div className="mt-5 flex items-start gap-2 p-3 rounded-lg border border-amber-300 dark:border-amber-700/60 bg-amber-50 dark:bg-amber-900/20 text-xs">
709+
<AlertCircle className="h-4 w-4 text-amber-600 dark:text-amber-400 mt-0.5 flex-shrink-0" />
710+
<div>
711+
<p className="font-semibold text-amber-800 dark:text-amber-200">
712+
Gateway is stopped
713+
</p>
714+
<p className="text-amber-700 dark:text-amber-300 mt-0.5">
715+
Start it from the Dashboard first, otherwise your IDE will sit at
716+
&quot;initialize&quot; forever.
717+
</p>
718+
</div>
719+
</div>
720+
)}
721+
</CardContent>
722+
</Card>
723+
724+
{/* Reuse the shared ConnectIDEs grid from the Dashboard so users
725+
don't have to navigate away. */}
726+
<ConnectIDEs
727+
gatewayUrl={gatewayStatus.url || 'http://localhost:45818'}
728+
gatewayRunning={gatewayStatus.running}
729+
/>
730+
</div>
731+
)
620732
) : (
621733
<div className="grid gap-5 auto-fill-cards">
622734
{filteredClients.map((client) => {

0 commit comments

Comments
 (0)