Surfaces (Draft)

Draft: content may change before publication.

Planned for: StablEdge Desktop 0.4.5 (design stage, fields may change)

This page comes from the 0.4.5 design and is not part of the released protocol yet. Until 0.4.5 ships, a manifest containing the fields below is refused by the current version. Once it ships, this page will follow the final schema.

0.4.5 plans to add four optional arrays to contributions so a plugin can appear somewhere other than a sidebar panel: a standalone window, a top-level page, the profile bar and the editor. All of them refer to a declared workspaces[].id, load that workspace's entry page and inherit its permission grants. The install page gains a group headed "This plugin will appear in…" that lists these places one by one.

New contributions at a glance

FieldMaximumWhere it appears
windows8A standalone window
pages1An entry in the top-level navigation, plus a full page
profile_items2A status item and menu in the profile bar
editors8An editor that opens files by extension (declared only in 0.4.5)

First-phase limits

  • No Agent binding and no agent.trigger: calling agent.* from a new surface returns PLUGIN_WORKSPACE_PERMISSION_DENIED. Keep features that work with the Agent in a sidebar workspace.
  • A window loads only pages from the package: in 0.4.5 a window can open only a workspace page declared in the manifest; loading an external web page waits for 0.4.6. Use ui.open_external for external links.
  • editors is declaration only: 0.4.5 validates editors and shows them on the install page under "will appear in…", while really opening a file with a plugin waits for 0.4.6.

windows: standalone windows

{
  "id": "float",
  "title": "Floating timer",
  "workspace": "main",
  "chrome": "none",
  "resizable": false,
  "taskbar": true,
  "always_on_top": true,
  "transparent": true,
  "shadow": false,
  "size": { "width": 320, "height": 200 },
  "min_size": { "width": 64, "height": 64 },
  "max_size": { "width": 640, "height": 400 },
  "position": "bottom_right",
  "remember_bounds": true,
  "single_instance": true,
  "layouts": [
    { "id": "orb", "size": { "width": 64, "height": 64 }, "anchor": "bottom_right" },
    { "id": "card", "size": { "width": 320, "height": 200 }, "anchor": "bottom_right" }
  ]
}
FieldValuesNotes
idlocal identifierUnique within the plugin.
title1–80Window title.
workspacea workspace idThe workspace the window loads; it must already be declared.
chromeapp | noneapp: a title bar in the app's own style. none: frameless, so every pixel except the host's close badge belongs to the plugin.
resizabledefault trueWhether the window can be resized.
taskbardefault trueWhether to show a taskbar item; setting it to false requires ui.window.hide_taskbar.
always_on_topdefault falseWhether the window stays on top; setting it to true requires ui.window.always_on_top.
transparentdefault falseWhether the window is transparent; it can only be set to true when chrome is none, and that requires ui.window.transparent.
shadowdefault trueWhether the window has a shadow.
size / min_size / max_size{ width, height }Initial, minimum and maximum size in logical pixels.
positioncascade (default) | center | top_left | top_right | bottom_left | bottom_rightInitial position: cascade from the window that opened it, centred, or one of the four screen corners. Absolute coordinates are not accepted.
remember_boundsdefault trueRestore the last position and size when the window reopens; fall back to position when it would land off screen.
single_instancedefault trueOpening it again just focuses the existing window.
layouts≤8 items of { id, size, anchor }Preset layouts you can switch between; anchor is the corner or center that stays put when switching.

Size bounds

  • With chrome set to none, the width and height of size, min_size, max_size and layouts[].size range from 48 to 3840; with app (and the pending system) they range from 120 to 3840.
  • min_size ≤ size ≤ max_size must hold, and layouts[].size must sit between min_size and max_size too. A small floating ball needs a small enough min_size.
  • At runtime the host also keeps a window inside the work area of its display, and never lets it cover a full-screen app.
  • A plugin can open at most 4 windows at once, and 12 in total across the app.

The host's safety net

A window with chrome set to none keeps a small hot zone in its top right corner that the plugin cannot cover, and the host's close badge appears when the pointer enters it. Every plugin window, including one with its taskbar item hidden, appears in the host's "Plugin windows" list, where you can find it and close it. Disabling, uninstalling or upgrading a plugin, or changing its permissions, closes its windows at once; signing out closes every plugin window.

pages: top-level pages

At most 1 entry: { id, title, workspace, icon? }. title is 1–32 characters, and icon refers to a resource id in resources. The host adds an entry to the top-level navigation and renders that workspace as a full page.

profile_items: the profile bar

At most 2 entries: { id, label, icon?, menu }. label is 1–32 characters; menu holds at most 6 entries, each { id, label, opens: { kind, id } }, where kind is workspace, window or page and id must refer to a matching declared item. Status text is updated at runtime through profile.set_status, stays within 48 characters, and the host shows it as plain text without embedding an iframe.

editors: editors (declaration only)

At most 8 entries: { id, title, workspace, extensions, priority, mode }. extensions holds 1–16 extensions (^[a-z0-9]{1,16}$); priority is default or option; mode is readonly or readwrite.

0.4.5 only validates the declaration and shows it on the install page under "will appear in…"; readwrite passes validation but is treated as read-only. Opening a file with a plugin really arrives in 0.4.6.

New permissions

All four are workspace-scoped permissions written with worker set to host, and the grant is recorded against the workspace the window refers to. The three sensitive ones are best declared optional; the install page ticks them separately and warns about the risk of imitating system or host interface elements.

PermissionWhen it is neededWithout the grant
ui.windowwindows is not emptywindow.open returns PLUGIN_WINDOW_PERMISSION_DENIED
ui.window.always_on_topA window sets always_on_top: trueDegrades to a window that does not stay on top
ui.window.hide_taskbarA window sets taskbar: falseDegrades to showing the taskbar item
ui.window.transparentA window sets transparent: trueDegrades to an opaque window

Declaring a field that needs a sensitive permission without requesting that permission makes the install fail with PLUGIN_MANIFEST_INVALID. You can query what actually took effect through window.capabilities.

Window API

Bridge methodSDK (stabledgePlugin.window.*)Notes
window.openwindow.openParameters { window_id, user_activated }, and it must be called inside a user action; with single_instance an already open window is focused and its handle returned.
window.closewindow.closeOmit the handle to close the calling window; a plugin can only close its own windows.
window.focuswindow.focusNeeds user activation, so a plugin cannot grab focus.
window.set_titlewindow.setTitleUp to 80 characters of plain text, which also changes the taskbar title.
window.set_layoutwindow.setLayoutCan only switch to a layout declared in layouts.
window.start_dragwindow.startDraggingStarts dragging the window.
window.capabilitieswindow.capabilitiesReturns what actually took effect: platform, chrome, transparent, always_on_top, taskbar, rounded_corners and layouts.
profile.set_status—Parameters { item_id, text }, updating the status text in the profile bar.

Inside a window the SDK treats elements with the [data-se-drag-region] attribute as drag regions, except those with [data-se-no-drag]. A window handle is an opaque string.

Events (subscribe with window.on): window.opened, window.closed (where reason is user, plugin, disabled, logout, host_exit or crashed) and window.focus_changed.

Error codes: PLUGIN_WINDOW_NOT_DECLARED, PLUGIN_WINDOW_LIMIT_REACHED, PLUGIN_WINDOW_PERMISSION_DENIED, PLUGIN_WINDOW_NOT_FOUND, PLUGIN_WINDOW_LAYOUT_NOT_DECLARED, PLUGIN_WINDOW_USER_ACTIVATION_REQUIRED and PLUGIN_WINDOW_OPEN_FAILED.

Reserved plugin names

At install and upgrade a plugin's name (display names in every language included) is normalized: surrounding whitespace stripped, full-width and half-width forms and case unified, and spaces and common separators removed. The result is compared against two lists of reserved names:

  • Level A: app names (StablEdge and its Chinese name) and system window names (Task Manager, System Settings, User Account Control and their English names). A name that is identical to one of them, starts with one, or ends with one is refused.
  • Level B: short words from the host interface (Settings, Plugins, Skills, Knowledge base, Sign in and their English forms). Only an exact match is refused, so a normal name such as "Translation plugin" is unaffected.

A hit makes the install fail with PLUGIN_MANIFEST_INVALID, naming the reserved name that was hit. Bundled plugins and plugins whose signature checks out against the official public key are exempt. The check applies to new installs and upgrades only, and is not applied retroactively to plugins already installed.

Open questions

  • chrome set to "system" (using the native system title bar); transparency is not allowed with that value. TBD
  • The ui.window.position permission, which would let a plugin set window coordinates. TBD
  • The host prefixing every window title with "plugin name · ", leaving the plugin to change only the second half. TBD
On this page