dock: the shell keeps its own items, sidecars contribute theirs
ALL_DOCK_ITEMS was a hardcoded list of everything, so a fresh machine offered Photos, Jellyfin, Transmission and the rest — each leading to a screen reporting itself unavailable — and adding a sidecar meant editing the shell. Neither survives sidecars shipping from their own repositories. Split in two. CORE_DOCK_ITEMS is the baseline that exists on every install: chat, files, terminal, the app's own screens, and Gitea, which is in the light profile because it fronts a remote instance. Everything else is derived from installed sidecars' UI manifests, delivered with /capabilities. Sent with the capability answer rather than fetched separately so the dock has ONE source. Two requests means two moments, and a dock rendered between them shows a tile for something uninstalled or nothing for something installed. Filtered by capability server-side too: a member is not handed the manifest of a feature they cannot use, because "hidden in the client" is the kind of privacy that lasts until someone opens the network tab. Verified live. The owner — who bypasses every permission check — does not bypass this: /photos is absent from routes and present in deniedRoutes because Photos is not installed. Flipping a row's `enabled` makes its tile leave and return with no process touched. Two things fell out. A manifest can declare extraTiles, because CalDAV is one sidecar presenting as Calendar AND Contacts, and collapsing them to keep the model tidy would make the app worse. And DEFAULT_DOCK_PATHS no longer pins /music: useDock drops a path with nothing behind it, so the default dock came up a tile short on any machine where Music was never installed — a default that references an optional feature is how an app looks subtly wrong on a fresh install for no stated reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,29 @@ export type SelfCapabilities = {
|
||||
* guard that cannot tell those apart either blanks the app or guards nothing.
|
||||
*/
|
||||
deniedRoutes: string[];
|
||||
/** Capabilities the account holds whose sidecar is not installed, or is installed but disabled. */
|
||||
unavailable?: string[];
|
||||
/**
|
||||
* Dock tiles and routes of the sidecars actually installed on this server.
|
||||
*
|
||||
* The dock is the baseline shell plus these. A feature appears when it is installed and leaves when it
|
||||
* is removed, with nothing in the shell to edit — which is what lets a sidecar ship from its own
|
||||
* repository and still show up.
|
||||
*/
|
||||
plugins?: PluginManifest[];
|
||||
};
|
||||
|
||||
/** What an installed sidecar says about how it should appear. Mirrors UiManifest on the server. */
|
||||
export type PluginManifest = {
|
||||
sidecarId: string;
|
||||
capability: string | null;
|
||||
name: string;
|
||||
icon?: string;
|
||||
image?: string;
|
||||
color: string;
|
||||
rootRoute: string;
|
||||
routes: string[];
|
||||
extraTiles?: Array<{ name: string; icon?: string; image?: string; color: string; route: string }>;
|
||||
};
|
||||
|
||||
export const CAPABILITIES_QUERY_KEY = ['self-capabilities'];
|
||||
@@ -80,6 +103,9 @@ export function useCapabilities() {
|
||||
isOwner: data?.isOwner ?? false,
|
||||
capabilities: held,
|
||||
routes: data?.routes ?? [],
|
||||
// Empty rather than undefined when the request has not landed or failed: the dock then renders its
|
||||
// baseline, which is the honest "we do not know yet" — not an empty app.
|
||||
plugins: data?.plugins ?? [],
|
||||
isLoading,
|
||||
isError,
|
||||
can,
|
||||
|
||||
Reference in New Issue
Block a user