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