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:
2026-08-11 18:51:41 +00:00
co-authored by Claude Opus 5
parent b4f88ec161
commit eda004a46d
3 changed files with 33 additions and 89 deletions
@@ -103,12 +103,6 @@ capabilityAdminRouter.get('/capabilities', ownerGate, async (ctx) => {
// Only the grantable kinds are offered. `execution` and `admin` are deliberately absent: a UI that
// shows a checkbox it will refuse to honour is worse than one that never offered it.
capabilities: GRANTABLE_CAPABILITIES.filter((c) => !unavailable.has(c.key)).map(describe),
/**
* Grantable, but their sidecar is not installed. Returned rather than dropped so the screen can say
* "these appear once you install them" — otherwise an owner who remembers seeing Photos here concludes
* the list is broken, and the honest answer is one sentence.
*/
notInstalled: GRANTABLE_CAPABILITIES.filter((c) => unavailable.has(c.key)).map(describe),
// Roles a grant may name. Super Admin is excluded: the owner bypasses this table entirely, and the
// database refuses a row for that role.
roles: USER_ROLES.filter((r) => r !== 'Super Admin'),