Files
soulseek/web/SoulseekNav.tsx
officer c11f45c033 soulseek, extracted from the platform as a plugin
Seventh plugin out, and the first that was already dead. Soulseek had been
switched off since 2026-08-13: hono.ts had the router import and its mount
commented out, so createSidecarProxy never ran, registerProxiedPrefix was never
called, the slskd:server announcement fired into a void, and officer-slskd was
not in the ecosystem at all. The permission entry had been reduced to api: [],
which is why assertPermissionTotality was satisfied and the server booted
clean. The UI rendered perfectly and every request 404'd.

So this is a resurrection as well as a move.

The mount is now /api/soulseek, derived via mountPrefix from the appName — all
47 frontend call sites repointed from /slskd/. The credential key in
service_connections stays 'slskd': it names the daemon, not the route. Those
two strings differ on purpose and conflating them reads the wrong row.

The connection stays in core service_connections, as transmission does, so
'slskd' remains in a platform union this plugin cannot extend. Recorded in
PLUGIN.md. Unlike transmission this plugin does own a schema: favourites and
the two halves of the cached browse tree, which slskd has no concept of. Those
three tables had been dropped from the drizzle barrel, so bun db:push would
have deleted them on any server holding data.

313 palette classes converted to the semantic tone set, including a shared
PHASE_TONE the three transfer views previously each copied.
2026-08-19 02:55:02 +00:00

78 lines
2.6 KiB
TypeScript

import type { LucideIcon } from 'lucide-react';
import { NavLink } from 'react-router';
import {
LayoutGrid,
Search,
ArrowDownToLine,
ArrowUpFromLine,
Hash,
MessageCircle,
Users,
Server,
Plug,
} from 'lucide-react';
import { SOULSEEK_SECTIONS, soulseekSectionPath, type SoulseekSectionId } from './shared';
// Left panel of the /soulseek workspace — a vertical section menu mirroring slskd's top nav. Entries are
// react-router NavLinks to /soulseek/<section>, so active state comes from the router and SoulseekView
// (right) learns which section is open by reading the same URL rather than by being told.
const ICONS: Record<SoulseekSectionId, LucideIcon> = {
dashboard: LayoutGrid,
search: Search,
downloads: ArrowDownToLine,
uploads: ArrowUpFromLine,
rooms: Hash,
chat: MessageCircle,
users: Users,
system: Server,
connection: Plug,
};
export const SoulseekNav = () => {
return (
<div className="flex h-full flex-col overflow-y-auto bg-muted/30">
<div className="flex items-center gap-3 px-4 py-4">
<div className="flex h-9 w-9 items-center justify-center rounded-xl bg-white shadow-sm ring-1 ring-black/5">
<img src="/plugins/soulseek/icon.png" alt="" className="h-5 w-5 object-contain" />
</div>
<div className="min-w-0">
<div className="truncate text-sm font-semibold leading-tight">Soulseek</div>
<div className="truncate text-xs text-muted-foreground">slskd client</div>
</div>
</div>
<nav className="flex flex-col gap-0.5 px-2 pb-3">
{SOULSEEK_SECTIONS.map(({ id, label }) => {
const Icon = ICONS[id];
return (
<NavLink
key={id}
to={soulseekSectionPath(id)}
className={({ isActive }) =>
`group relative flex items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors ${
isActive
? 'bg-primary/10 font-medium text-primary'
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
}`
}
>
{({ isActive }) => (
<>
{isActive && (
<span className="absolute left-0 top-1/2 h-5 w-1 -translate-y-1/2 rounded-r-full bg-primary" />
)}
<Icon
className={`h-4 w-4 shrink-0 ${isActive ? 'text-primary' : 'text-muted-foreground group-hover:text-foreground'}`}
/>
{label}
</>
)}
</NavLink>
);
})}
</nav>
</div>
);
};