a plugin can put an action in the file browser's context menu

Tasks already reach that menu and are right for "do this to these files" — a
script, run as the owner, reported through the job system, needing no frontend.

They are the wrong shape for anything that continues in the interface. "Create
Gallery" from a folder is not a job to watch; it is a name to type and a screen
to land on, and expressing that as a script would mean a job whose only output is
somewhere to go next.

So a plugin exports `fileActions` from `web/file-actions.ts` and the generated
module collects them. Presence on disk is the declaration, exactly as panels.ts
and layout.ts already are — the manifest says nothing, because an action carries
its own label and decides for itself whether it applies.

They are registered by injection rather than imported by the browser.
Plugins.gen.tsx is generated INTO the app, and the file browser lives in a
workspace package that app depends on; importing it there would point the
dependency backwards and make the package unusable outside Officer's frontend.

The selection is resolved by FileGrid, which has it, and FileItem is handed
ready-made items. An action receives the whole selection when the right-clicked
item is part of one and just that item otherwise — what every file manager does;
acting on a selection the click was not part of is the surprising alternative.

An action whose `appliesTo` throws hides itself rather than breaking the menu for
everything else on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 20:59:20 +01:00
co-authored by Claude Opus 5
parent e756007551
commit a6d4a18592
9 changed files with 196 additions and 5 deletions
+11 -1
View File
@@ -8,7 +8,17 @@ import { useInitialData } from '@/state/useInitialData';
// `installedPlugins`, not `plugins`: App.tsx already destructures a `plugins` from useServerSettings(),
// which is the DEAD plugin system — /server-settings/plugins scans src/workspaces/plugins/, a directory
// that does not exist, so it is always []. Different thing entirely; see plugins/offscale/PLUGIN.md.
import { plugins as installedPlugins } from './Plugins.gen';
import { plugins as installedPlugins, pluginFileActions } from './Plugins.gen';
import { registerFileActions } from 'officerdev';
/*
Plugin actions for the file browser's context menu, handed over once at module load.
Injected rather than imported by the browser itself: `Plugins.gen.tsx` is generated INTO this app, and
the file browser lives in a workspace package this app depends on. Importing it there would point the
dependency backwards and make that package unusable outside Officer's own frontend.
*/
registerFileActions(pluginFileActions);
/**
* `/plugins` → `/marketplace`, KEEPING the query string.