Skip to content

Commit e1c5d0a

Browse files
committed
refactor(ui): reorganize IA + design refresh on the superapp blueprint
Restructure the existing desktop app (no new features) so future superapp surfaces (Chat, Agents, Models) are config additions, not layout work — per mcpmux.space/superapp/03-experience-design.md. Information architecture: - Navigation is now data (lib/navigation.ts): a top "use" zone (Home) and Library / Control zones; Settings pinned to the sidebar footer. Adding a future page = one NavEntry. - Newcomer naming, plumbing words demoted to subtitles: Dashboard → Home, My Servers → Tools, Built-in Servers → Context, Clients → Apps; SpacesPage heading fixed from "Workspaces" → "Spaces" (collided with the actual Workspaces page); Discover gets a Compass icon (was a duplicate Server). - NavItem store keys and every data-testid are unchanged (ADR-003 contract). Design refresh (on-template: flat tints + solid accent strips, no gradients): - Sidebar: accent-strip active state, refined zone labels, hover micro- motion, hint tooltips on every item. - Home extracted from App.tsx into features/home/HomePage.tsx; stat tiles redesigned as clickable shortcuts (accent strip, tinted icon, hover lift) that navigate to the page they count. - New shared PageHeader (@mcpmux/ui) — one header pattern (tracking-tight title, capped subtitle, pinned actions); adopted by Tools, Apps, and Home. - Per-Space accent system (lib/spaceAccent.ts): deterministic curated hue per Space id, applied to the SpaceSwitcher glyph tile and the status-bar Space dot — instant context awareness across the app. Tests updated for renamed labels (testids untouched): wdio + Playwright specs and page objects (Dashboard→Home, My Servers→Tools, Connections→Apps, Built-in Servers→Context, stat-card labels, welcome copy). Verified: vitest 174 passed, tsc clean, eslint 0 errors, production web build green. Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 96ac65e commit e1c5d0a

36 files changed

Lines changed: 1721 additions & 1285 deletions

apps/desktop/src/App.tsx

Lines changed: 104 additions & 259 deletions
Large diffs are not rendered by default.

apps/desktop/src/components/SpaceSwitcher.tsx

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,32 @@ import { ChevronDown, Check, Plus, Loader2 } from 'lucide-react';
33
import { Button, useToast, ToastContainer } from '@mcpmux/ui';
44
import { useAppStore, useViewSpace, useSpaces, useIsLoading } from '@/stores';
55
import { createSpace } from '@/lib/api/spaces';
6+
import { spaceAccentTint } from '@/lib/spaceAccent';
7+
8+
/** Space icon inside a soft tile tinted with the Space's accent color. */
9+
function SpaceGlyph({
10+
spaceId,
11+
icon,
12+
size = 'md',
13+
}: {
14+
spaceId: string | undefined;
15+
icon: string | undefined | null;
16+
size?: 'md' | 'sm';
17+
}) {
18+
return (
19+
<span
20+
className={`flex flex-shrink-0 items-center justify-center rounded-lg ${
21+
size === 'md' ? 'h-8 w-8 text-lg' : 'h-7 w-7 text-base'
22+
}`}
23+
style={{
24+
backgroundColor: spaceAccentTint(spaceId, 0.16),
25+
boxShadow: `inset 0 0 0 1px ${spaceAccentTint(spaceId, 0.35)}`,
26+
}}
27+
>
28+
{icon || '🌐'}
29+
</span>
30+
);
31+
}
632

733
interface SpaceSwitcherProps {
834
className?: string;
@@ -68,56 +94,62 @@ export function SpaceSwitcher({ className = '' }: SpaceSwitcherProps) {
6894
{/* Trigger Button */}
6995
<button
7096
onClick={() => setIsOpen(!isOpen)}
71-
className="w-full flex items-center justify-between gap-2 px-3 py-2.5 rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] hover:bg-[rgb(var(--surface-hover))] hover:border-[rgb(var(--primary))/30] transition-all duration-150 group"
97+
className="group flex w-full items-center justify-between gap-2 rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] px-3 py-2.5 transition-all duration-150 hover:border-[rgb(var(--primary))/30] hover:bg-[rgb(var(--surface-hover))]"
7298
>
73-
<span className="flex items-center gap-3 min-w-0">
99+
<span className="flex min-w-0 items-center gap-2.5">
74100
{isLoadingSpaces ? (
75101
<Loader2 className="h-5 w-5 animate-spin text-[rgb(var(--primary))]" />
76102
) : (
77-
<span className="text-xl">{viewSpace?.icon || '🌐'}</span>
103+
<SpaceGlyph spaceId={viewSpace?.id} icon={viewSpace?.icon} />
78104
)}
79-
<span className="font-medium text-sm truncate">
80-
{isLoadingSpaces
81-
? 'Loading...'
82-
: viewSpace?.name || (spaces.length > 0 ? 'Select Space' : 'No Spaces')}
105+
<span className="min-w-0">
106+
<span className="block truncate text-sm font-medium">
107+
{isLoadingSpaces
108+
? 'Loading...'
109+
: viewSpace?.name || (spaces.length > 0 ? 'Select Space' : 'No Spaces')}
110+
</span>
111+
{!isLoadingSpaces && viewSpace && (
112+
<span className="block text-[10px] uppercase tracking-wider text-[rgb(var(--muted-foreground))]">
113+
Space
114+
</span>
115+
)}
83116
</span>
84117
</span>
85118
<ChevronDown
86-
className={`h-4 w-4 flex-shrink-0 text-[rgb(var(--muted))] group-hover:text-[rgb(var(--foreground))] transition-all duration-200 ${isOpen ? 'rotate-180' : ''}`}
119+
className={`h-4 w-4 flex-shrink-0 text-[rgb(var(--muted))] transition-all duration-200 group-hover:text-[rgb(var(--foreground))] ${isOpen ? 'rotate-180' : ''}`}
87120
/>
88121
</button>
89122

90123
{/* Dropdown */}
91124
{isOpen && (
92-
<div className="dropdown-menu absolute top-full left-0 mt-1.5 w-64 z-50 animate-in fade-in slide-in-from-top-1 duration-150">
125+
<div className="dropdown-menu animate-in fade-in slide-in-from-top-1 absolute left-0 top-full z-50 mt-1.5 w-64 duration-150">
93126
{/* Spaces List */}
94-
<div className="p-1.5 max-h-64 overflow-y-auto">
127+
<div className="max-h-64 overflow-y-auto p-1.5">
95128
{isLoadingSpaces ? (
96129
<div className="flex items-center justify-center py-4">
97-
<Loader2 className="h-5 w-5 animate-spin text-primary-500" />
130+
<Loader2 className="text-primary-500 h-5 w-5 animate-spin" />
98131
<span className="ml-2 text-sm text-[rgb(var(--muted))]">Loading spaces...</span>
99132
</div>
100133
) : spaces.length === 0 ? (
101-
<div className="text-center py-4 text-sm text-[rgb(var(--muted))]">
134+
<div className="py-4 text-center text-sm text-[rgb(var(--muted))]">
102135
No spaces found. Create one below.
103136
</div>
104137
) : (
105138
spaces.map((space) => (
106139
<button
107140
key={space.id}
108141
onClick={() => handleSelectSpace(space.id)}
109-
className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg text-left transition-all duration-150
110-
${
111-
viewSpace?.id === space.id
112-
? 'bg-[rgb(var(--primary))/12] text-[rgb(var(--primary))]'
113-
: 'hover:bg-[rgb(var(--surface-hover))]'
114-
}`}
142+
className={`flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-left transition-all duration-150 ${
143+
viewSpace?.id === space.id
144+
? 'bg-[rgb(var(--primary))/12] text-[rgb(var(--primary))]'
145+
: 'hover:bg-[rgb(var(--surface-hover))]'
146+
}`}
115147
data-testid={`space-switcher-item-${space.id}`}
116148
>
117-
<span className="flex items-center gap-3 min-w-0">
118-
<span className="text-xl flex-shrink-0">{space.icon || '🌐'}</span>
149+
<span className="flex min-w-0 items-center gap-3">
150+
<SpaceGlyph spaceId={space.id} icon={space.icon} size="sm" />
119151
<div className="min-w-0">
120-
<div className="font-medium text-sm truncate">{space.name}</div>
152+
<div className="truncate text-sm font-medium">{space.name}</div>
121153
{space.is_default && (
122154
<div
123155
className="text-xs text-[rgb(var(--muted))]"
@@ -168,7 +200,7 @@ export function SpaceSwitcher({ className = '' }: SpaceSwitcherProps) {
168200
) : (
169201
<button
170202
onClick={() => setShowCreateInput(true)}
171-
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm text-[rgb(var(--muted))] hover:bg-[rgb(var(--surface-hover))] hover:text-[rgb(var(--foreground))] transition-all duration-150"
203+
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm text-[rgb(var(--muted))] transition-all duration-150 hover:bg-[rgb(var(--surface-hover))] hover:text-[rgb(var(--foreground))]"
172204
>
173205
<Plus className="h-4 w-4" />
174206
Create new space

apps/desktop/src/features/builtinServers/BuiltinServersPage.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
/**
2-
* Built-in Servers page.
2+
* Context page (formerly "Built-in Servers").
33
*
4-
* Home for the MCP servers McpMux ships itself — distinct from the servers the
5-
* user installs (those live under "My Servers"). Built-in servers and their
6-
* individual tools are enabled/disabled **per Space**: this page configures the
7-
* Space currently selected in the sidebar's Space switcher.
4+
* Home for the capabilities McpMux itself provides to connected apps —
5+
* distinct from the servers the user installs (those live under "Tools").
6+
* Built-in servers and their individual tools are enabled/disabled
7+
* **per Space**: this page configures the Space currently selected in the
8+
* sidebar's Space switcher.
89
*
910
* Today there's one concrete built-in server — "Tool Optimization" (the
1011
* `mcpmux_*` self-management toolset). Memory / Skills / Plugins are scaffolded
11-
* as "coming soon" so the shape of the framework is visible.
12+
* as "coming soon" — per the superapp plan they all land on this page.
1213
*/
1314

1415
import { useEffect, useState } from 'react';
@@ -165,12 +166,13 @@ export function BuiltinServersPage() {
165166
<div className="flex h-full flex-col" data-testid="builtin-servers-page">
166167
<header className="flex-shrink-0 border-b border-[rgb(var(--border-subtle))] p-8">
167168
<div className="mx-auto max-w-[2000px]">
168-
<h1 className="text-3xl font-bold">Built-in Servers</h1>
169+
<h1 className="text-3xl font-bold">Context</h1>
169170
<p className="mt-2 max-w-2xl text-base text-[rgb(var(--muted))]">
170-
MCP servers that ship with McpMux — separate from the ones you install under{' '}
171-
<span className="font-medium text-[rgb(var(--foreground))]">My Servers</span>. Enable
172-
them and toggle their tools <span className="font-medium">per Space</span>; the choices
173-
below apply to{' '}
171+
Capabilities McpMux itself gives your AI apps — separate from the servers you install
172+
under <span className="font-medium text-[rgb(var(--foreground))]">Tools</span>.
173+
Self-management ships today; Memory, Skills, and Plugins are coming. Enable them and
174+
toggle their tools <span className="font-medium">per Space</span>; the choices below
175+
apply to{' '}
174176
<span className="font-semibold text-[rgb(var(--foreground))]">
175177
{space?.name ?? '…'}
176178
</span>{' '}
@@ -183,7 +185,7 @@ export function BuiltinServersPage() {
183185
<div className="mx-auto max-w-3xl space-y-6">
184186
{loading ? (
185187
<div className="flex items-center justify-center py-16">
186-
<Loader2 className="h-6 w-6 animate-spin text-primary-500" />
188+
<Loader2 className="text-primary-500 h-6 w-6 animate-spin" />
187189
</div>
188190
) : (
189191
servers.map((server) => (
@@ -192,7 +194,7 @@ export function BuiltinServersPage() {
192194
<div className="flex items-start justify-between gap-4">
193195
<div className="min-w-0">
194196
<CardTitle className="flex items-center gap-2">
195-
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary-500/10 text-primary-500">
197+
<span className="bg-primary-500/10 text-primary-500 flex h-8 w-8 items-center justify-center rounded-lg">
196198
{SERVER_ICONS[server.id] ?? <Boxes className="h-5 w-5" />}
197199
</span>
198200
{server.name}

0 commit comments

Comments
 (0)