a naked platform does not describe what it does not have
Reversing my own call from an hour ago. I built the denied-route screen to EXPLAIN the absence — "Music is not installed", with a link to the app store — and argued a redirect erases what you asked for. The owner's correction is the better principle: a server should not know about a sidecar it does not have. Explaining Music is the app describing a feature that, as far as this install is concerned, does not exist, and it leaks the whole catalogue of what could be installed to any member who types a URL. So a denied path is now indistinguishable from an unknown one: redirect home, the same answer App.tsx's path="*" already gave. One behaviour for a member without a grant, an owner without the sidecar, and a typo. Nothing disclosed. The Permissions screen loses both explanatory blocks for the same reason. One listed every capability whose sidecar is absent — a catalogue of uninstallable features presented as a permissions decision. The other described chat, tasks, the desktop and the wallet as "not grantable" to an owner who may have none of them installed. `notInstalled` is gone from the API too, not just hidden in the UI. What is on that screen is what this server can actually do. Still short of what the owner described, and worth naming rather than implying otherwise: routes are DECLARED in App.tsx for every screen and this hides the ones that should not resolve. The end state is routes REGISTERED from the manifests of installed sidecars, so an uninstalled feature has no route to hide. The manifests already exist and the dock is already built from them; the router is not, yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+8
-33
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
import { Loader2, Lock, PackageOpen } from 'lucide-react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { CAPABILITIES_QUERY_KEY } from 'hooks/useCapabilities';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -29,8 +29,7 @@ type Grant = { role: string; capability: string; level: 'read' | 'write' };
|
||||
type CapabilitiesResponse = {
|
||||
/** Grantable AND installed. What this server can currently do. */
|
||||
capabilities: CapabilityInfo[];
|
||||
/** Grantable, but no sidecar installed — listed so their absence reads as a fact, not a bug. */
|
||||
notInstalled: CapabilityInfo[];
|
||||
|
||||
roles: string[];
|
||||
grants: Grant[];
|
||||
};
|
||||
@@ -164,36 +163,12 @@ export const PermissionsSection = () => {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Absent because nothing is installed, not because they cannot be shared — a different sentence from
|
||||
the one below, and the two used to be indistinguishable (both were simply missing). */}
|
||||
{data.notInstalled.length > 0 && (
|
||||
<div className="flex gap-3 rounded-lg border border-dashed p-3 text-xs text-muted-foreground">
|
||||
<PackageOpen className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
<div className="font-medium text-foreground">Nothing installed for these yet</div>
|
||||
{data.notInstalled.map((c) => c.label).join(', ')} — each appears here once you install it from the App
|
||||
store. Granting something this server cannot do would only produce a refusal the person could not explain.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stated rather than silently omitted. An owner who cannot find the Terminal checkbox will assume
|
||||
the screen is incomplete and go looking for it; saying why it does not exist is the difference
|
||||
between a deliberate design and a missing feature. */}
|
||||
<div className="flex gap-3 rounded-lg border border-dashed p-3 text-xs text-muted-foreground">
|
||||
<Lock className="mt-0.5 h-4 w-4 shrink-0" />
|
||||
<div>
|
||||
<div className="font-medium text-foreground">Not listed, and not grantable</div>
|
||||
Chat, tasks, capability authoring, the desktop and the browser run as the server owner, in the server
|
||||
owner’s home directory, with full permissions. Granting one would hand over the machine rather than a
|
||||
feature, so there is no level at which they can be shared. The wallet, Headscale and the server settings stay
|
||||
with the owner for the same reason.
|
||||
<div className="mt-1.5">
|
||||
Files is the exception, and only because of how it is built: a member with a Linux account on this machine
|
||||
gets their own home, enforced by the operating system rather than by a check in the app.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Two explanatory blocks used to sit here: one naming every capability whose sidecar is not installed,
|
||||
and one naming everything that can never be granted. Both are gone, and for the same reason — a
|
||||
server should not enumerate what it does not have. The first was a catalogue of uninstallable
|
||||
features presented as a permissions decision; the second described chat, tasks, the desktop and the
|
||||
wallet to an owner who may have none of them installed. What is on this screen is what this server
|
||||
can actually do. */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user