the platform stops knowing what VNC is

Desktop is a plugin as of today, and this is the half that leaves. sidecar/vnc/, api/desktop/ and
apps/Desktop/ are gone from src/, along with the vocabulary that was the real problem.

sidecar/protocol.ts had a VNC section: vnc:start, vnc:ensure-password, vnc:stop, four reply variants
and two types. sidecar-registry.ts had startVnc, ensureVncPassword, stopVnc, isVncConnected. Four
functions and a dozen message types in the platform, describing a feature that is not the platform's —
the rule CLAUDE.md states most plainly, and its clearest breach.

What replaced them is generic. sendSidecarCommand(kind, cmd) and isSidecarConnected(kind) know that a
command has a type and an id and that a reply carries the same id. A plugin's sidecar speaks whatever
it likes through them and nothing here has to be edited when it adds a message.

The desktop PERMISSION leaves too, and that is the interesting part: it was kind: 'execution', a kind
a plugin permission cannot have and never will, because a permission nobody can delegate is a
contradiction. The plugin expresses the same refusal as ownerOnly: ['/'] — every path under it denied
to anyone but the owner. Verified: /api/desktop, /api/desktop/vnc-password and a path that does not
exist all resolve owner-only. registry.test.ts records the round trip rather than just dropping the
key from its list.

Also gone: the app store's `vnc` entry, which would have offered an older second way to install the
same thing against a process PM2 no longer has, and four commented-out mounts left from 2026-08-13.

The disconnect-scope test used startVnc as its stand-in for "another sidecar's long-running request".
It now uses the generic door, which is the better instrument — that file is about the blast radius of
a disconnect and never had anything to do with VNC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 00:42:19 +00:00
co-authored by Claude Opus 5
parent b1a2d67b76
commit fcdbf02649
20 changed files with 67 additions and 929 deletions
+1 -1
View File
@@ -111,7 +111,7 @@ describe('member provisioning is declared for every entry', () => {
// against an API that does not exist. Transmission was an example until 2026-08-18 and slskd until
// 2026-08-19, when they became `plugins/transmission/` and `plugins/soulseek/` — hence the list
// rather than a single id: it must stay non-empty, or this assertion passes by iterating nothing.
for (const id of ['vnc', 'notify']) expect(byId(id)!.members).toBe('none');
for (const id of ['notify']) expect(byId(id)!.members).toBe('none');
});
// A test asserting the vault is invite-only lived here until 2026-08-18, when it left to become
+3 -14
View File
@@ -239,20 +239,9 @@ export const CATALOGUE: CatalogueEntry[] = [
permission: 'notify',
configFields: [],
},
{
id: 'vnc',
ui: { name: 'Desktop', icon: 'MonitorSmartphone', color: '#ec4899', rootRoute: '/desktop', routes: ['/desktop'] },
process: 'officer-vnc',
label: 'Desktop',
summary: 'Mirror this machine’s display in the browser',
members: 'none',
modes: ['config'],
permission: 'desktop',
// x11vnc against an Xorg display. There is nothing to mirror on a headless box or on macOS, so the
// store should say so rather than install something that starts and immediately fails.
requires: 'linux-display',
configFields: [],
},
// The `vnc` entry left with the desktop plugin on 2026-08-21. The app store predates the marketplace,
// and a plugin is installed through that now — an entry here would offer a second, older way to install
// the same thing, against a process PM2 no longer has.
];
export const byId = (id: string): CatalogueEntry | undefined => CATALOGUE.find((e) => e.id === id);
+2 -2
View File
@@ -12,7 +12,7 @@ describe('what adding a member would do', () => {
it('predicts the outcome per service without contacting anything', () => {
expect(plannedOutcome(byId('memos')!)).toBe('provisioned');
expect(plannedOutcome(inviteOnly)).toBe('invited');
expect(plannedOutcome(byId('vnc')!)).toBe('not-applicable');
expect(plannedOutcome(byId('notify')!)).toBe('not-applicable');
});
});
@@ -24,7 +24,7 @@ describe('which installed services have work to do', () => {
// two. It has been re-pointed five times as services left — jellyfin, invoiceshelf and slskd to
// `plugins/`, caldav to CORE — and memos is the last one standing. If memos ever leaves too, this
// wants a literal fixture like `installer.test.ts` has, not a sixth re-point.
const ids = servicesNeedingMemberProvision(['memos', 'vnc']).map((e) => e.id);
const ids = servicesNeedingMemberProvision(['memos', 'notify']).map((e) => e.id);
expect(ids.sort()).toEqual(['memos']);
});