super-productivity/packages/plugin-dev/automations
Johannes Millan f94a46decf feat(plugin-automations): add taskStarted/taskStopped triggers and removeTag action
Adds two new triggers and a new action to the bundled automations plugin,
along with the host-side and validator changes needed to make them
reliable end to end.

Plugin

- New triggers TriggerTaskStarted / TriggerTaskStopped that fire on timer
  start / stop. Switching from task A to task B emits taskStopped(A) and
  then taskStarted(B), so a rule subscribed to either trigger gets the
  full lifecycle. The trigger descriptions document this explicitly.
- New ActionRemoveTag mirroring ActionAddTag, with a shared resolveTagId
  helper covering id/title lookup, missing-tag warning, and idempotent
  short-circuiting.
- Import goes through a new RuleRegistry.addRules(rules[]) + addRules
  message, so the host's 1 call/sec persistDataSynced rate limit no
  longer rejects multi-rule imports.
- Validators in core/rule-registry.ts and utils/rule-validator.ts share a
  set of `as const` arrays guarded by a TS _AssertEq exhaustiveness check
  against the union types in types.ts. The arrays are duplicated rather
  than exported from types.ts because that would turn types.ts into a
  shared runtime chunk and break plugin.js (which the host evaluates via
  `new Function`, not as an ES module).
- Import validator no longer requires a truthy rule name — the UI saves
  empty-named rules, the load-path accepts them, and the import path
  needed to match.
- ActionDialog gets a removeTag placeholder. Manifest's hooks list is
  updated to include the hooks the plugin actually registers
  (taskCreated, currentTaskChange) so the permission disclosure UI is
  accurate.

Host

- plugin-hooks.effects.ts onCurrentTaskChange$ now observes
  selectCurrentTask directly instead of only setCurrentTask /
  unsetCurrentTask actions. This catches transitions through reducer
  paths that don't dispatch those actions (loadAllData, deleteProject,
  bulk task delete via the shared CRUD meta-reducer).
- The payload changes from the raw new Task to { current, previous },
  matching the long-declared CurrentTaskChangePayload shape. The effect
  uses pairwise on the selector and filters same-id pairs at the event
  boundary (not via distinctUntilChanged on the source) so the
  `previous` task always carries the latest snapshot — including
  in-place updates a plugin made while the task was running. Without
  this, addTag-on-start / removeTag-on-stop only worked every other
  cycle because `previous` reflected the pre-mutation snapshot.

Other plugins consuming currentTaskChange (voice-reminder, api-test-plugin)
read payload.current instead of the raw Task.
2026-05-14 21:14:00 +02:00
..
scripts refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00
src feat(plugin-automations): add taskStarted/taskStopped triggers and removeTag action 2026-05-14 21:14:00 +02:00
.gitignore refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00
.prettierrc refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00
eslint.config.js chore(deps): upgrade ESLint to v9 with flat config 2026-01-10 16:08:11 +01:00
package-lock.json chore(deps): bump the npm_and_yarn group across 1 directory with 3 updates (#7332) 2026-04-23 13:54:43 +02:00
package.json build(deps): bump the npm_and_yarn group across 4 directories with 1 update (#6141) 2026-01-25 10:48:01 +01:00
README.md build: update links to match our new organization 2026-01-05 14:45:06 +01:00
tsconfig.json refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00
vite.config.ts refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00

Solid.js Boilerplate Plugin for Super Productivity

A modern, TypeScript-based boilerplate for creating Super Productivity plugins using Solid.js.

Features

  • 🚀 Solid.js - Fast, reactive UI framework
  • 📘 TypeScript - Full type safety with Super Productivity Plugin API
  • 🎨 Modern UI - Clean, responsive design with dark mode support
  • 🔧 Vite - Lightning-fast development and build tooling
  • 📦 Ready to Use - Complete setup with examples for all plugin features

Getting Started

Prerequisites

  • Node.js 16+
  • npm or yarn
  • Super Productivity 8.0.0+

Installation

  1. Clone this boilerplate:
cd packages/plugin-dev
cp -r boilerplate-solid-js my-plugin
cd my-plugin
  1. Install dependencies:
npm install
  1. Update plugin metadata in src/manifest.json:
    • Change id to a unique identifier
    • Update name, description, and author
    • Modify permissions and hooks as needed

Development

Run the development server:

npm run dev

This starts Vite in watch mode. Your plugin will rebuild automatically when you make changes.

Building

Build the plugin for production:

npm run build

This creates optimized files in the dist/ directory.

Packaging

Create a ZIP file for distribution:

npm run package

This will:

  1. Build the plugin
  2. Create a ZIP file containing all necessary files
  3. Place the ZIP in the root directory

Deployment (for Plugins with HTML UI)

If your plugin has an index.html file (for UI components, side panels, etc.), use the deploy command instead:

npm run deploy

This will:

  1. Build the plugin
  2. Inline all CSS and JavaScript assets into the HTML file
  3. Create a ZIP file for distribution

Note: The deploy command is necessary for any plugin with HTML UI because Super Productivity loads plugin HTML as data URLs, which cannot access external files. The inline-assets script ensures all assets are embedded directly in the HTML.

Project Structure

src/
├── assets/          # Static assets (icons, images)
│   └── icon.svg     # Plugin icon
├── app/             # Solid.js application
│   ├── App.tsx      # Main app component
│   └── App.css      # App styles
├── index.html       # Plugin UI entry point
├── index.ts         # UI initialization
├── plugin.ts        # Plugin logic and API integration
└── manifest.json    # Plugin metadata

scripts/            # Build and utility scripts
└── build-plugin.js  # Plugin packaging script

dist/               # Build output (gitignored)
├── assets/
├── index.html
├── index.js
├── plugin.js
└── manifest.json

Plugin API Usage

Basic Setup

The plugin API is exposed through the global plugin object in plugin.ts:

import { PluginInterface } from '@super-productivity/plugin-api';

declare const plugin: PluginInterface;

Common API Methods

UI Registration

// Register header button
plugin.registerHeaderButton({
  icon: 'rocket',
  tooltip: 'Open Plugin',
  action: () => plugin.showIndexHtmlAsView(),
});

// Register menu entry
plugin.registerMenuEntry({
  label: 'My Plugin',
  icon: 'rocket',
  action: () => plugin.showIndexHtmlAsView(),
});

// Register keyboard shortcut
plugin.registerShortcut({
  keys: 'ctrl+shift+m',
  label: 'Open My Plugin',
  action: () => plugin.showIndexHtmlAsView(),
});

Data Operations

// Get tasks
const tasks = await plugin.getTasks();
const archivedTasks = await plugin.getArchivedTasks();

// Create task
const newTask = await plugin.addTask({
  title: 'New Task',
  projectId: 'project-id',
});

// Update task
await plugin.updateTask('task-id', {
  title: 'Updated Title',
  isDone: true,
});

// Get projects and tags
const projects = await plugin.getAllProjects();
const tags = await plugin.getAllTags();

Event Hooks

// Task completion
plugin.on('taskComplete', (task) => {
  console.log('Task completed:', task.title);
});

// Task updates
plugin.on('taskUpdate', (task) => {
  console.log('Task updated:', task);
});

// Context changes
plugin.on('contextChange', (context) => {
  console.log('Context changed:', context);
});

Communication with UI

In plugin.ts:

plugin.onMessage('myCommand', async (data) => {
  // Handle message from UI
  return { result: 'success' };
});

In your Solid.js component:

const sendMessage = async (type: string, payload?: any) => {
  return new Promise((resolve) => {
    const messageId = Math.random().toString(36).substr(2, 9);

    const handler = (event: MessageEvent) => {
      if (event.data.messageId === messageId) {
        window.removeEventListener('message', handler);
        resolve(event.data.response);
      }
    };

    window.addEventListener('message', handler);
    window.parent.postMessage({ type, payload, messageId }, '*');
  });
};

// Usage
const result = await sendMessage('myCommand', { foo: 'bar' });

Customization

Styling

The boilerplate includes:

  • CSS custom properties for theming
  • Dark mode support
  • Responsive design
  • Minimal, clean styling

Modify src/app/App.css to customize the appearance.

Adding Features

  1. New UI Components: Add them in src/app/ as .tsx files
  2. New API Endpoints: Add handlers in src/plugin.ts using plugin.onMessage()
  3. New Hooks: Register them in manifest.json and handle in plugin.ts
  4. Permissions: Add required permissions to manifest.json

Best Practices

  1. Type Safety: Always use TypeScript types from @super-productivity/plugin-api
  2. Error Handling: Wrap async operations in try-catch blocks
  3. Performance: Use Solid.js signals and effects efficiently
  4. Security: Never expose sensitive data or operations
  5. User Experience: Provide loading states and error feedback

Deployment

  1. Build the plugin: npm run build
  2. Package it: npm run package
  3. Upload the ZIP file to Super Productivity:
    • Open Super Productivity
    • Go to Settings → Plugins
    • Click "Upload Plugin"
    • Select your ZIP file

Troubleshooting

Plugin not loading

  • Check browser console for errors
  • Verify manifest.json is valid JSON
  • Ensure minSupVersion matches your Super Productivity version

API calls failing

  • Check if you have required permissions in manifest.json
  • Verify Super Productivity is running the correct version
  • Look for error messages in the console

Build errors

  • Run npm run typecheck to check for TypeScript errors
  • Ensure all dependencies are installed
  • Clear node_modules and reinstall if needed

Resources

License

This boilerplate is provided as-is for creating Super Productivity plugins. Feel free to modify and distribute your plugins as you see fit.