diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/Spotlight.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/Spotlight.tsx index 1de24ef9..561da96f 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/Spotlight.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/Spotlight.tsx @@ -84,16 +84,37 @@ const Row = ({ active ? 'bg-accent' : 'hover:bg-accent/50' }`} > + {/* + The dock's tile, at row size. Same two cases and the same rule for choosing between them, because + these are the SAME things and a second visual language for them is what made this list read as + somebody else's menu: + + badge a filled square of the item's colour with a white glyph — a white glyph on nothing is + invisible, which is why the fill is not decoration + bare no background, artwork fills the box — a logo framed in an arbitrary swatch reads as a + mistake, and every plugin shipping its own art is this case + + The tinted `${color}22` square this used for everything was a third style belonging to neither, and + it put a coloured pane behind Calendar, Contacts and every plugin logo. + */} {item.image ? ( - + ) : Icon ? ( - + ) : ( - + )} diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/useSpotlightItems.ts b/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/useSpotlightItems.ts index 783e4132..be0964ee 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/useSpotlightItems.ts +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Spotlight/useSpotlightItems.ts @@ -1,6 +1,23 @@ import { useMemo } from 'react'; import type { LucideIcon } from 'lucide-react'; -import { Settings, User, Puzzle, Server, Cpu, Users } from 'lucide-react'; +import { + Bot, + CalendarDays, + Cpu, + Globe, + KeyRound, + LayoutGrid, + Lock, + Mail, + Mic, + ScanText, + ShieldCheck, + Sparkles, + User, + Users, + Volume2, + Wrench, +} from 'lucide-react'; import { usePermissions } from 'hooks/usePermissions'; import { CORE_DOCK_ITEMS, dockItemsFromPlugins } from '../Dock'; @@ -32,6 +49,15 @@ export type SpotlightItem = { /** Plugin artwork, when the tile has some. */ image?: string; color?: string; + /** + * Copied from the dock tile, and it has to be. + * + * `bare` means the artwork brings its own frame — calendar, contacts, and every plugin shipping a logo. + * Dropping it here painted a coloured swatch behind those logos, which the dock's own comment calls out + * as reading like a mistake. It was dropped, so Spotlight looked like a different app's list of the same + * things. + */ + tile?: 'badge' | 'bare'; }; /** @@ -41,19 +67,67 @@ export type SpotlightItem = { * will not appear here until someone adds it. The alternative is exporting every settings screen's * section array up into the shell, which couples five screens to this one to save five lines. * - * Worth revisiting if it drifts. `hint` is what makes them findable — typing "keys" should reach the API - * keys screen, and the label alone does not say where it lives. + * IT DRIFTED, and within two days. The first version listed nine rows against a settings area that has + * twenty-one, missed Languages and Voice entirely, pointed `/settings/users` at a route that does not + * exist (it is `/settings/user-management/users`), and gave four different rows the same User glyph — + * which is most of why the whole panel read as a generic list rather than as the app's own. + * + * So the cost is now measured rather than predicted, and deriving these from the screens is the fix if it + * happens a third time. Each group below names the file it is copied from, so checking is one grep. + * + * `hint` is what makes them findable — typing "keys" should reach the API keys screen, and the label alone + * does not say where it lives. */ const SETTINGS_ITEMS: SpotlightItem[] = [ + // Profile — ProfileSettings/index.tsx { id: 'set:profile', label: 'Profile', to: '/settings/profile', hint: 'avatar, name, email', icon: User }, - { id: 'set:password', label: 'Change password', to: '/settings/profile/change-password', icon: User }, - { id: 'set:api-keys', label: 'API keys', to: '/settings/profile/api-keys', hint: 'per-app keys', icon: User }, - { id: 'set:dock', label: 'Dock', to: '/settings/profile/dock', hint: 'choose and reorder', icon: User }, - { id: 'set:integrations', label: 'Integrations', to: '/settings/integrations', icon: Puzzle }, - { id: 'set:server', label: 'Server settings', to: '/settings/server', icon: Server }, - { id: 'set:system', label: 'System', to: '/settings/system', icon: Cpu }, - { id: 'set:users', label: 'Users', to: '/settings/users', hint: 'accounts and permissions', icon: Users }, - { id: 'set:ai', label: 'AI', to: '/settings/ai', hint: 'models and harnesses', icon: Settings }, + { id: 'set:password', label: 'Change password', to: '/settings/profile/change-password', icon: Lock }, + { + id: 'set:api-keys', + label: 'API keys', + to: '/settings/profile/api-keys', + hint: 'keys that sign in as you', + icon: KeyRound, + }, + { + id: 'set:languages', + label: 'Languages', + to: '/settings/profile/languages', + hint: 'spoken and translation', + icon: Globe, + }, + { id: 'set:voice', label: 'Voice', to: '/settings/profile/voice', hint: 'text-to-speech', icon: Volume2 }, + { id: 'set:dock', label: 'Dock', to: '/settings/profile/dock', hint: 'choose and reorder', icon: LayoutGrid }, + // AI — AISettings.tsx + { id: 'set:ai-my-models', label: 'My Models', to: '/settings/ai/my-models', icon: Sparkles }, + { id: 'set:ai-channel-models', label: 'Channel Models', to: '/settings/ai/member-models', icon: Sparkles }, + { id: 'set:ai-default-models', label: 'Default Models', to: '/settings/ai/default-models', icon: Sparkles }, + { id: 'set:ai-harnesses', label: 'Providers', to: '/settings/ai/ai-harnesses', hint: 'harnesses', icon: Bot }, + // System — SystemSettings.tsx + { id: 'set:tts', label: 'Text to Speech', to: '/settings/system/tts', icon: Volume2 }, + { id: 'set:stt', label: 'Speech to Text', to: '/settings/system/stt', icon: Mic }, + { id: 'set:ocr', label: 'OCR', to: '/settings/system/ocr', icon: ScanText }, + { + id: 'set:maintenance', + label: 'Rebuild & restart', + to: '/settings/system/maintenance', + hint: 'maintenance', + icon: Cpu, + }, + { id: 'set:smtp', label: 'SMTP', to: '/settings/system/smtp', hint: 'outgoing mail', icon: Mail }, + // Integrations — IntegrationsSettings + { id: 'set:google-oauth', label: 'Google OAuth', to: '/settings/integrations/google-oauth', icon: KeyRound }, + { id: 'set:apify', label: 'Apify', to: '/settings/integrations/apify', icon: Wrench }, + { + id: 'set:dav', + label: 'Calendar & Contacts sync', + to: '/settings/integrations/dav-app-passwords', + hint: 'app passwords', + icon: CalendarDays, + }, + // Users — UserManagement + { id: 'set:users', label: 'Accounts', to: '/settings/user-management/users', hint: 'users', icon: Users }, + { id: 'set:permissions', label: 'Permissions', to: '/settings/user-management/permissions', icon: ShieldCheck }, ]; /** Everything Spotlight can offer this account, in a stable order. */ @@ -70,6 +144,7 @@ export function useSpotlightItems(): SpotlightItem[] { icon: item.icon, image: item.image, color: item.color, + tile: item.tile, })); // Settings go through `canVisit` too. `usePermissions` fails OPEN by design — hiding a door is a