import { useMemo } from 'react'; import type { LucideIcon } from 'lucide-react'; import { Settings, User, Puzzle, Server, Cpu, Users } from 'lucide-react'; import { usePermissions } from 'hooks/usePermissions'; import { CORE_DOCK_ITEMS, dockItemsFromPlugins } from '../Dock'; // What Spotlight can take you to. // // ── It indexes the DOCK, rather than a list of its own ── // // The dock is already the composed, permission-filtered answer to "what can this account open here": // the shell's own screens, plus whatever installed plugins contribute, minus anything `canVisit` refuses. // Building a second list would mean two answers to one question, and the second would be wrong the day // somebody installs a plugin — which is the whole failure the dock's own comment warns about, arrived at // from a third direction. // // So a new plugin appears in Spotlight for the same reason it appears in the dock: because it is // installed. Nothing here is edited. // // Settings sections are added because they are the things people hunt for and the dock deliberately does // not carry. They are static: a settings screen is shell, not a plugin. export type SpotlightItem = { /** Stable across renders — used as the React key and for the recency store. */ id: string; label: string; /** Where it goes. Matched against `canVisit` for anything not already filtered. */ to: string; /** Shown small beside the label, so two "Profile"s are distinguishable. */ hint?: string; icon?: LucideIcon; /** Plugin artwork, when the tile has some. */ image?: string; color?: string; }; /** * The settings sections, which the dock does not carry. * * Hand-listed rather than derived, and that is a real cost: a section added to `IntegrationsSettings` * 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. */ const SETTINGS_ITEMS: SpotlightItem[] = [ { 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 }, ]; /** Everything Spotlight can offer this account, in a stable order. */ export function useSpotlightItems(): SpotlightItem[] { const { canVisit, plugins } = usePermissions(); return useMemo(() => { const fromDock: SpotlightItem[] = [...CORE_DOCK_ITEMS, ...dockItemsFromPlugins(plugins)] .filter((item) => canVisit(item.to)) .map((item) => ({ id: `nav:${item.to}`, label: item.label, to: item.to, icon: item.icon, image: item.image, color: item.color, })); // Settings go through `canVisit` too. `usePermissions` fails OPEN by design — hiding a door is a // courtesy and the 403 is the lock — so this cannot lock anyone out of their own settings. const settings = SETTINGS_ITEMS.filter((item) => canVisit(item.to)); return [...fromDock, ...settings]; }, [canVisit, plugins]); }