dms_tailscalectl/skills/dms-plugin-dev/docs/cheatsheets.md
vybe 01ac7e9041 feat: add dms-plugin-dev agent skill for Dank Material Shell plugin development
- Introduces a general-purpose opencode skill to help agents build, debug, and publish DMS plugins.
- Includes orientation, decision trees (plugin types), condensed cheat sheets, ecosystem map, and four generic vertical-slice starter templates (bar widget, popout widget, launcher, desktop widget).
- Skill is versioned here for this project while remaining available as a global opencode skill.
- Updated .gitignore to ignore globally-installed copies of the skill.

Written by AI agent working for @jtmorris. Model: grok-build-0.1.
2026-05-24 09:31:06 +00:00

5.9 KiB

DMS Plugin Cheat Sheets

These are deliberately condensed surfaces. For full details and current property lists, read the official plugin-development guide for the exact DMS version in use.

plugin.json — Fields That Actually Matter

Required for all:

  • id (camelCase, unique, used for reload and data keys)
  • name, description, version, author
  • type: "widget" | "launcher" | "daemon" | "desktop"
  • component: relative path to the root QML file

Strongly recommended:

  • icon (Material icon name shown in the Plugins list)
  • permissions: array — "settings_write" is required if you provide a settings component using PluginSettings
  • capabilities: helps the UI know where to offer the plugin (e.g. ["dankbar-widget"], ["control-center"], ["desktop-widget"], ["launcher"])
  • settings: relative path to the settings QML (only if you want a UI in Settings → Plugins)

Launcher-specific:

  • trigger: the prefix string (or "" for always-visible)
  • viewMode: "list" | "grid" | "tile"
  • viewModeEnforced: boolean

Desktop-specific:

  • requires_dms: e.g. ">=1.2.0"

requires (array of external tools) is used for documentation and install hints.

PluginComponent (the base for almost every widget)

Auto-injected (never declare these yourself):

  • pluginData — reactive object containing everything saved via the settings system for this plugin
  • pluginService
  • pluginId
  • axis, section, parentScreen, widgetThickness, barThickness, iconSize, variants

You define:

  • horizontalBarPill: Component { ... }
  • verticalBarPill: Component { ... } (can be the same or different layout)
  • popoutContent: Component { PopoutComponent { ... } } (optional but very common)
  • popoutWidth, popoutHeight
  • layerNamespacePlugin: "unique-name" (strongly recommended for any popout widget)
  • pillClickAction / pillRightClickAction (to override the default popout behavior)

For Control Center presence, also define:

  • ccWidgetIcon, ccWidgetPrimaryText, ccWidgetSecondaryText, ccWidgetIsActive
  • onCcWidgetToggled
  • ccDetailContent (for the expandable detail panel on CompoundPill)

PopoutComponent

Convenience wrapper that gives you a consistent header + details area + closePopout() function.

Expose headerHeight and detailsHeight so your content can compute the remaining space correctly.

PluginService Data APIs (three different stores)

  1. Settings (savePluginData / loadPluginData)

    • Persisted in the shared plugin_settings.json
    • Shown/edited via the *Setting components inside PluginSettings
    • Use for user preferences
  2. State (savePluginState / loadPluginState)

    • Per-plugin _state.json file in ~/.local/state/DankMaterialShell/plugins/
    • Not shown in settings UI
    • Good for command history, recent items, runtime caches that should survive reloads
  3. Global Variables (getGlobalVar / setGlobalVar + PluginGlobalVar helper)

    • In-memory, reactive across all instances of the same plugin
    • Not persisted
    • Perfect for "current selection", counters, shared UI state on multi-monitor bars

The modern settings UI components (StringSetting, ToggleSetting, ColorSetting, SliderSetting, SelectionSetting, ListSettingWithInput, etc.) live inside a PluginSettings { pluginId: "..." } root and handle load/save automatically.

Launcher Plugin Shape (different from widgets)

Root must be a QtObject { id: root ... }

You must provide (at minimum):

  • getItems(query) → array of item objects
  • executeItem(item)

Optional but powerful:

  • getContextMenuActions(item) → array of {icon, text, action, closeLauncher?}
  • getCategories() + setCategory(id)
  • getPasteText(item) and/or getPasteArgs(item) for Shift+Enter paste support

Item shape includes name, icon (or unicode:...), comment, action, imageUrl, animated, attribution, categories, keywords.

Call pluginService.requestLauncherUpdate(pluginId) after async data arrives.

External Commands

Prefer Proc.runCommand(id, argv, (stdout, exitCode) => {...}, debounceMs) from qs.Common.

It gives you captured stdout, automatic cleanup, and debouncing by id.

Only fall back to a raw Process item when you truly need a long-lived or streaming process.

Desktop Widgets (DesktopPluginComponent)

  • Set minWidth / minHeight (and optionally defaultWidth / defaultHeight, forceSquare)
  • Read widgetWidth, widgetHeight, pluginData
  • Use the helper methods getData(key, default) and setData(key, value) if you want the thin wrappers
  • Position/size is persisted automatically per screen
  • User interaction: right-click-drag anywhere to move, right-click-drag bottom-right corner to resize

Permissions (the one that bites agents)

If your plugin has a settings component that uses PluginSettings, you must list "settings_write" in the permissions array.

Without it the settings UI will show an error instead of your form.

Other permissions (settings_read, process, network) are declared for documentation and future enforcement.

Layer Namespaces for Popouts

On any PluginComponent that opens a popout:

layerNamespacePlugin: "my-unique-popout"

This gives the popout its own layer namespace under dms:plugins:... and avoids focus/z-order fights with other DMS popouts.

Hot Reload & Debugging

  • dms ipc call plugins reload <id>
  • dms ipc call plugins list
  • dms ipc call plugins status <id>
  • Run the shell from a terminal (dms run or equivalent) so you see QML errors and console.log

Theme, Toast, I18n

Always go through the singletons:

  • Theme.* for colors, spacing, font sizes, corner radii
  • ToastService.showInfo(...) / showError(...)
  • I18n.tr("text", "context for translators")

Never hard-code colors or sizes.

This is enough to get 80% of plugins correct on the first serious attempt. For the remaining 20%, go to the official development guide for the exact property lists and edge cases.