spotlight draws the dock's tiles, and lists the settings that actually exist
Two things made it read as a generic list rather than as this app's own, and neither was the item source —
that was already the dock, and plugins were already in it.
TILES. SpotlightItem dropped `tile`, so every row got a `${color}22` tinted square: a third visual language
belonging to neither of the dock's two. Which put a coloured pane behind Calendar, Contacts and every
plugin's logo — the exact thing the dock's own comment calls out as reading like a mistake. Rows now use the
dock's rule: a filled square of the item's colour with a white glyph, or no background at all when the
artwork brings its own frame. A settings row with no colour keeps a muted glyph instead of getting a swatch
invented for it.
SETTINGS. The hand-listed sections had drifted within two days of being written, and the file's own comment
predicted it ("worth revisiting if it drifts"). Nine rows against a settings area that has twenty-one:
Languages and Voice missing entirely, every AI and System section missing, `/settings/users` pointing at a
route that does not exist — it is `/settings/user-management/users` — and four different rows sharing the
User glyph, which is most of what made the panel look generic.
Now twenty rows, copied from the five screens that declare them, with each group naming its source file so
checking is one grep. The comment records that the drift HAPPENED rather than predicting it, and says
deriving them from the screens is the fix if it happens a third time.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
*/}
|
||||
<span
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md"
|
||||
style={item.color ? { backgroundColor: `${item.color}22`, color: item.color } : undefined}
|
||||
style={{
|
||||
background: item.tile === 'bare' ? 'transparent' : (item.color ?? 'transparent'),
|
||||
// No colour at all is a settings row: keep the muted glyph rather than inventing a swatch.
|
||||
color: item.color ? '#fff' : undefined,
|
||||
}}
|
||||
>
|
||||
{item.image ? (
|
||||
<img src={item.image} alt="" className="h-5 w-5 object-contain" />
|
||||
<img
|
||||
src={item.image}
|
||||
alt=""
|
||||
className={item.tile === 'bare' ? 'h-full w-full object-contain' : 'h-5 w-5 object-contain'}
|
||||
/>
|
||||
) : Icon ? (
|
||||
<Icon className="h-4 w-4" />
|
||||
<Icon className={`h-4 w-4 ${item.color ? '' : 'text-muted-foreground'}`} />
|
||||
) : (
|
||||
<Search className="h-4 w-4" />
|
||||
<Search className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user