Keyboard shortcuts

← →
Previous or next page
S /
Search
?
Show this help
Esc
Close

External file providers

File providers add state-dependent context actions and file decorations without loading provider code into Strata. Examples include cloud availability and version-control status. Providers are opt-in trusted programs, not sandboxes; they run with the user's permissions and receive visible native paths. Only install a provider from a source you trust.

Static custom actions continue to use Custom actions. They do not change. A file provider is appropriate when eligibility and state come from an external service and must update while a directory/menu stays open. The browser does not need to know about cloud accounts, pins or service sockets.

Registration and artwork

Install one directory per provider under $XDG_CONFIG_HOME/strata/providers/ (default ~/.config/strata/providers/), then restart Strata. For example:

{
  "version": 1,
  "id": "example",
  "name": "Example Cloud",
  "command": ["/usr/bin/python3", "/home/alice/.config/strata/providers/example/provider.py"],
  "icon": "logo",
  "icons": {"logo": "logo.png", "available": "available.png", "working": "working.png"}
}

Save this as example/provider.json. The manifest must set version to 1 and be at most 16 KiB. The id is 1–64 ASCII letters, digits or dashes and must match the directory name. The optional name supplies the human-readable menu group heading; without it, Strata uses the id. Names must contain non-whitespace plain text, at most 64 UTF-8 bytes, without control characters. The optional icon names one of the declared icons and is drawn before that heading. If icon names an id that is not in icons, Strata rejects the registration and the provider does not load. Omitting icon or setting it to null is accepted. Existing registrations need no changes. command holds 1 to 16 strings of at most 4,096 bytes each, with no NUL. Commands are literal argv with an absolute executable; no shell interpolation, PATH lookup, placeholder expansion or working-directory executable lookup is performed. The child runs with / as its working directory. Nothing in a browsed folder registers a provider. Removing the registration and restarting Strata uninstalls it. Editing a registration takes effect on restart. A relative XDG_CONFIG_HOME is ignored, and the default location is used.

The providers directory itself, each registration directory, each manifest and each icon file must be owned by the current user or root, must not be group- or world-writable, and cannot be a symlink. If the providers directory fails this check, for example because a dotfile manager made it a symlink, no provider loads. The executable, command[0], is checked after its symlinks are resolved: the target must be a regular file owned by the current user or root and must not be group- or world-writable. A script passed as a later argument is not checked. A registration that fails any of these checks does not load. Providers can reference only declared icon ids in replies. Icon ids are 1–64 ASCII letters, digits or dashes. PNG artwork lives beside the manifest (plain filenames ending in .png, no subdirectories), at most 64 KiB and 256×256 per image, up to 16 icons. It is loaded once from trusted configuration, never from selected file content or URLs. Branded artwork preserves its colors; use artwork legible on both light and dark backgrounds. Strata's own interface continues to use themed Lucide icons.

Transport

The child reads newline-delimited UTF-8 JSON requests on stdin and writes JSON responses/events on stdout. Flush each line. Stdin/stdout are a private full-duplex socket, not a terminal. Protocol data only belongs on stdout. Provider stderr is discarded, so do not rely on it for user-facing failures or print secrets there.

Every request has version: 1, a unique numeric id, method, paths (absolute UTF-8 strings), and background (boolean). A background menu has the current folder as its single path. Non-native and non-UTF-8 paths are not sent. Newlines, quotes, spaces and Unicode in filenames are JSON data, not delimiters or commands.

query asks for decorations for up to 200 paths:

{"version":1,"id":1,"method":"query","paths":["/home/alice/cloud/report.txt"],"background":false}
{"version":1,"id":1,"decorations":[{"path":"/home/alice/cloud/report.txt","badge":"available","description":"Available offline"}]}

Omit a path or use badge: null to draw no decoration. Replies can only affect requested paths. A reply holds at most 200 decorations, and a description is at most 512 bytes. An optional priority is 0 (normal, the default), 1 (warning), or 2 (error); a higher value is invalid. The highest priority wins; equal priorities use the provider id in lexical order, independent of discovery timing. Accessible descriptions include all contributing providers, prefixed with their ids. Status badges do not use tooltips. Rows are rebound to their actual current path; old responses cannot decorate a reused row. Badges share the thumbnail/icon rendering used by Columns, List and Icons. The badge is drawn as a square over the bottom-right corner of the file's icon. Its side is 55% of the icon's shorter side, kept between 10 and 24 pixels. Non-square artwork is stretched to fit.

menu asks for actions for the entire selection, up to 200 items:

{"version":1,"id":2,"method":"menu","paths":["/home/alice/cloud/report.txt"],"background":false}
{"version":1,"id":2,"actions":[{"id":"keep","label":"Keep offline","icon":"available"}]}

Return an empty actions array for ineligible selections. No placeholder or disabled global menu item is inserted. Strata always keeps each provider's root entries together in an inline section with a small, muted, non-interactive heading. Provider sections appear directly after any custom actions and before the remaining built-in commands. When several providers answer, their sections follow the byte order of their registration directory names. This is not an extra submenu: providers choose flat actions, recursive submenus, or a mixture within their section:

{"version":1,"id":2,"actions":[{"id":"share","label":"Copy share link","context":"opaque-selection-token"},{"id":"offline","label":"Offline availability","children":[{"id":"keep","label":"Keep offline","icon":"available","context":"opaque-selection-token"}]}]}

Only leaves activate; a branch's nonempty children array is navigation only. Branches cannot have context. Across the entire tree, allow at most 64 nodes, four levels (roots are level one), and unique ids of 1–64 ASCII letters, digits or dashes. Labels are nonempty plain text, at most 128 UTF-8 bytes, without control characters. Icons are optional declared icon ids. Leaf context is an optional nonempty opaque string of at most 2,048 UTF-8 bytes. Action and submenu labels remain as supplied, without repeated provider suffixes. The section heading identifies their source using the registered name or id, including when different providers supply identical labels. Empty sections and their headings disappear. Menus populate asynchronously; retained provider sections and branches reuse their models on refresh. Removed leaves are disabled before removal, including when their containing section or branch is withdrawn or the menu closes.

Each menu and activation describes the whole selection, never a silently filtered subset. An adapter may support cross-account selections; otherwise return no actions. For parent/child selections, define whether a recursive operation covers descendants and report counts against the original selection. Deduplication or batching inside the adapter must preserve those semantics. Above 200 items Strata offers no provider actions; it does not truncate a menu or activation selection. Selections must also fit within a request frame: the JSON-escaped path array has a budget of 1 MiB minus 16 KiB reserved for the envelope and context. Larger selections receive no actions; decoration queries split into fitting batches. Oversized host requests are rejected before admission and do not kill an adapter.

activate repeats the exact selection/background, selected leaf action id and its context, if provided:

{"version":1,"id":3,"method":"activate","paths":["/home/alice/cloud/report.txt"],"background":false,"action":"keep","context":"opaque-selection-token"}
{"version":1,"id":3,"message":"Downloads continue in the service.","outcome":{"status":"accepted","accepted":1,"total":1,"job":"job-17"}}

Revalidate identity and eligibility on activation. A menu is not authorization to act on a replaced file, renamed path or new mount incarnation. Bind context to the exact selection, account/collection/item identities and mount incarnation; reject stale tokens. Enforce identity at the service's operation boundary, not just during a prior path lookup. Strata echoes the token unchanged and replaces it when the same leaf receives new context. A context can also be an adapter's idempotency token; the host neither interprets it nor manufactures replay safety. Adapters without tokens must still guarantee safe identity revalidation.

Every successful activation requires a nonempty plain-text message (at most 16 KiB). Messages and decoration descriptions may contain newlines, carriage returns and tabs, but no other control characters, including NUL or terminal escape sequences. An optional outcome object must include status: accepted, rejected, partial or unknown. A reply with an outcome but no status is an invalid frame and disconnects the provider. accepted and total must appear together, with total equal to the original selection size and 0 <= accepted <= total. Accepted means all submitted; rejected means none; partial means some but not all. Unknown may report a confirmed accepted lower bound while other results remain uncertain. Optional job is a nonempty service reference of at most 128 bytes without control characters. Strata shows the result in a File availability dialog. The dialog starts with the provider id, then a status (Accepted, Rejected, Partially accepted or Outcome unknown) with any counts, such as (1/1), then Job: <job>, then the message. An unknown outcome shows its counts as a lower bound: at least N/M accepted. Message-only legacy replies show the provider id and the message; they remain supported, but do not imply confirmed acceptance or completion. When the provider's queue is full, the dialog says the provider is busy or unavailable and the action was not sent. After a disconnect, it says either that the action was not sent or that it may already have been accepted.

Optional feedback chooses how a successful result is presented: dialog (the default), notice for a non-blocking notice in the window's progress dock, or none when the service presents its own interface, such as a share window. It is honored only with outcome.status accepted; rejected, partial and unknown outcomes, message-only replies, and disconnects always use the dialog. Unrecognized values also use the dialog. The message remains required either way. The notice shows the provider's name, or its id, above the message for about four seconds, and replaces any notice still visible. The notice wraps the message and cuts it off with an ellipsis after three lines. Where no notice can be shown, the File availability dialog opens instead, showing the provider's name, or its id, followed by the message.

{"version":1,"id":3,"message":"Link copied.","outcome":{"status":"accepted"},"feedback":"notice"}

After every activation reply, Strata treats all of that provider's answers as invalidated. It re-queries the provider's visible badges and open menus without waiting for an invalidate event.

Long operations should be submitted as service jobs. There is no automatic retry of actions, including after a lost reply. An unsent action is distinguished from an action whose outcome is unknown; a disconnect after submission must not be represented as proof that nothing happened. Partial acceptance is not a transaction rollback. Check service state before a manual retry.

To mark cached answers dirty, emit an event at any time. An optional paths array scopes it to 1–200 absolute native UTF-8 roots; omission means all paths, and an empty array is invalid:

{"version":1,"event":"invalidate","paths":["/home/alice/cloud"],"revision":17}

Use a provider-session monotonic unsigned revision for reliable snapshot ordering. A query/menu reply's revision identifies the coherent snapshot used for that selection. It must cover every preceding relevant invalidation; a reply older than such an event is rejected. Unrelated roots do not reject it. Invalidation roots overlap selections in either ancestor/descendant direction, at component boundaries; paths use lexical spelling, so adapters should consistently normalize their native roots without resolving selected content or symlinks.

Strata preserves wire order across events and replies. A change emitted after a valid snapshot marks it dirty for another refresh, while retaining presentation. This guarantees progress for coherent snapshots under sustained activity, without treating a reply older than a preceding relevant event as current. Once any revision is used, all subsequent invalidations and successful query/menu replies in that process session need revisions; invalidation revisions cannot decrease. Breaking either rule disconnects the provider. A process restart begins a new session and may restart its counter at zero.

Legacy unversioned events are refresh hints. Their query/menu replies must still be coherent snapshots at emission time; Strata accepts them while retaining the need to refresh if a hint crossed the request. Without revisions the host cannot detect an adapter emitting an obsolete snapshot. New adapters should use revisions and scoped invalidations rather than global hints for unrelated account activity.

Invalidate on mount disappearance, disconnect/reconnect or lost event history. An empty method-specific answer withdraws presentation immediately. An event alone does not erase a valid answer: it requests its replacement. Provider disconnects withdraw everything. A five-second refresh interval is a fallback; an answer not replaced expires after fifteen seconds. Identical answers do not rebuild menus or clear badges, and open submenu models survive retained-branch refreshes.

Compatibility

Unknown additive fields in manifests, requests and responses should be ignored; known fields must retain their types and bounds. A response has exactly one of id or event. The only event is invalidate. Any other event name, an event carrying decorations, actions, outcome or error or a nonempty message, and an id reply carrying paths are invalid frames and disconnect the provider. A valid reply whose id matches no outstanding request is ignored. Successful queries require decorations, menus require actions, and activations require message. Legacy adapters may additionally include empty actions/decorations arrays for another method. Missing required fields, duplicate tree ids, unknown icons and malformed outcomes are protocol errors. feedback belongs only to activation replies. A feedback value that is not a string or null is an invalid frame. So is feedback on a query, menu or error reply or on an event. Either one disconnects the provider. An unrecognized string is not an error; it uses the dialog. Providers must not send nonempty fields belonging to another method.

For an unsupported method or request, reply with the request id, a bounded slug error such as unsupported-method, and an optional human-readable message; omit actions, decorations, outcome and feedback. A method error withdraws that requested presentation without killing the provider. An error reply to activate opens the File availability dialog with the provider id and the optional message. Without a message, the dialog says "The provider did not report an outcome." Version 1 has a fixed baseline and no handshake: hosts only send the three methods above. Do not require a startup reply or a capabilities request. A future optional negotiation must preserve that baseline.

Bounds and failure behavior

At most eight registrations are loaded (from at most 64 directory entries), with one child per provider. Strata sends one request at a time on each connection; serial adapters are supported, and events may arrive at any time. It queues up to 16 speculative query/menu requests and eight activations separately. User actions have dispatch priority after the current request; pending refreshes for the same visible path/menu are coalesced by the host. A full queue rejects admission, never kills a healthy process. An action rejected at admission was not sent and is not retried. Replies have 32 reserved queue slots; reading events continues while new dispatch is paused for host backpressure. Events coalesce only between replies, with independent per-root revisions. More than 200 roots or 64 KiB of root strings in one event batch conservatively becomes a global invalidation.

Each response frame, excluding its newline, is limited to 1 MiB; adjacent valid frames do not share that bound. Native paths are at most 16,384 UTF-8 bytes. Requests time out eight seconds after dispatch, not admission; socket writes after 250 ms. Invalid frames, excessive output and timeouts disconnect the provider; its process group is terminated and its state is withdrawn. Strata starts a provider's process when it first sends that provider a request. After a disconnect, it waits two seconds and starts a new process at the next request. Actions in flight are reported as uncertain, unsent queued actions as unsent; neither is replayed. Disconnect updates retire only requests from the failed session; requests admitted afterward keep their outcome tracking across restart.

The GTK thread does not perform provider socket or subprocess I/O. It applies completed bounded batches on its main loop. Each provider cache holds at most 2,048 path answers and 32 menu selections; at most 1,024 live thumbnail slots are tracked. Only mapped slots are queried. Unmapped slots lose their decoration and re-register on mapping; slots refused at capacity retry admission while mapped.

Strata logs bounded static failure categories and validated provider ids for rejected registrations, spawn/transport failures, timeouts and malformed output. Repeated transport diagnostics are rate-limited to once per ten seconds. Host saturation is a separate busy/unsent condition, not an adapter protocol fault. Raw stdout, stderr, command arguments, selected paths, context tokens and messages are never included in these diagnostics.

This API does not authorize downloading file content to calculate presentation. Use cached metadata/service status and never inspect selected documents merely to draw a menu or badge. This is a provider obligation, not an enforced security boundary: a trusted child has the user's permissions. Strata's existing thumbnail/preview behavior is separate. Remote-only locations and non-UTF-8 filenames are unsupported; no lossy conversion is performed.