diff --git a/plugins/music/assets/icon.png b/plugins/music/assets/icon.png index 65981284..93b10cf2 100644 Binary files a/plugins/music/assets/icon.png and b/plugins/music/assets/icon.png differ diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx index d9ee1cf7..05656b3b 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx @@ -11,6 +11,14 @@ export type DockItem = { // Either a lucide glyph (rendered white on the coloured tile) or an image asset (e.g. an app favicon). icon?: LucideIcon; image?: string; + /** + * `badge` (the default) fills a rounded square with `color` and insets the icon — which is what a + * white lucide glyph needs, because on nothing it is invisible. `bare` draws no background and lets + * artwork fill the tile, because a logo framed in an arbitrary swatch reads as a mistake. + * + * `color` is still used either way: the active glow and the indicator dot are drawn from it. + */ + tile?: 'badge' | 'bare'; }; type DockProps = { @@ -101,12 +109,20 @@ export const Dock = ({ items, className, boundaryRef }: DockProps) => {
{item.image ? ( - + ) : ( item.icon && )} @@ -182,15 +198,30 @@ export const CORE_DOCK_ITEMS: DockItem[] = [ */ export function dockItemsFromPlugins(plugins: PluginManifest[]): DockItem[] { return plugins.flatMap((plugin) => { - const tile = (t: { name: string; icon?: string; image?: string; color: string; route: string }): DockItem => ({ + const tile = (t: { + name: string; + icon?: string; + image?: string; + tile?: 'badge' | 'bare'; + color: string; + route: string; + }): DockItem => ({ label: t.name, to: t.route, color: t.color, + ...(t.tile ? { tile: t.tile } : {}), ...(t.image ? { image: t.image } : { icon: resolveIcon(t.icon ?? 'Box') }), }); return [ - tile({ name: plugin.name, icon: plugin.icon, image: plugin.image, color: plugin.color, route: plugin.rootRoute }), + tile({ + name: plugin.name, + icon: plugin.icon, + image: plugin.image, + tile: plugin.tile, + color: plugin.color, + route: plugin.rootRoute, + }), ...(plugin.extraTiles ?? []).map(tile), ]; }); diff --git a/src/servers/plugins/manifest.ts b/src/servers/plugins/manifest.ts index c57927b0..e2737467 100644 --- a/src/servers/plugins/manifest.ts +++ b/src/servers/plugins/manifest.ts @@ -102,8 +102,25 @@ export type PluginManifest = { * `assets/icon.png`. Optional for that reason: a plugin with its own artwork has nothing to say here. */ icon?: string; - /** Tile colour. */ + /** + * Accent colour. Four jobs, not one: the dock tile's background in `badge` style, the active glow, + * the active indicator dot, and the tint on the plugins list. Required even for a plugin whose tile + * draws no background, because the other three still use it. + */ color: string; + /** + * How the dock draws this plugin's tile. + * + * badge a rounded square filled with `color`, the icon inset — what a lucide GLYPH needs, since a + * white glyph on nothing is invisible. + * bare no background; the artwork fills the tile — what a brand mark wants, since a logo framed + * in an arbitrary swatch reads as a mistake. + * + * DEFAULTED from whether the plugin ships `assets/icon.png`: artwork gets `bare`, a glyph gets + * `badge`. Presence is the declaration here as everywhere else, and this field only exists to + * override that — a plugin whose artwork genuinely wants a coloured backdrop can say `badge`. + */ + tile?: 'badge' | 'bare'; permissions: PluginPermission[]; diff --git a/src/servers/plugins/mount.ts b/src/servers/plugins/mount.ts index cc5168da..dfa04be8 100644 --- a/src/servers/plugins/mount.ts +++ b/src/servers/plugins/mount.ts @@ -184,6 +184,7 @@ export async function pluginDockManifests(): Promise< name: string; icon?: string; image?: string; + tile?: 'badge' | 'bare'; color: string; rootRoute: string; routes: string[]; @@ -202,6 +203,8 @@ export async function pluginDockManifests(): Promise< // artwork of its own. The dock renders `image` as an and `icon` through `resolveIcon`, // and it already preferred the image — this is the first thing to give it one. ...(plugin.icon ? { image: iconUrl(plugin.appName) } : { icon: plugin.manifest.icon }), + // Artwork defaults to `bare`, a glyph to `badge`; the manifest overrides either way. + tile: plugin.manifest.tile ?? (plugin.icon ? 'bare' : 'badge'), color: plugin.manifest.color, rootRoute: prefix, routes: [prefix], diff --git a/src/workspaces/hooks/src/useCapabilities.ts b/src/workspaces/hooks/src/useCapabilities.ts index ba78a645..50b8e2f5 100644 --- a/src/workspaces/hooks/src/useCapabilities.ts +++ b/src/workspaces/hooks/src/useCapabilities.ts @@ -46,6 +46,8 @@ export type PluginManifest = { name: string; icon?: string; image?: string; + /** `bare` draws no tile background — for a plugin whose icon is artwork rather than a glyph. */ + tile?: 'badge' | 'bare'; color: string; rootRoute: string; routes: string[];