super-productivity/packages/plugin-dev/automations
Symon Baikov d0da0aea93
Codex/issue 8081 keep subtask input open (#8423)
* fix(tasks): keep subtask input open after add

* fix(tasks): keep subtask input open after add

* perf(tasks): avoid task row effect reactivity

* fix(tasks): address subtask input review

* feat(tasks): ease in the inline subtask input instead of popping

* fix(tasks): consume inline subtask-input request to prevent stale focus-steal

Address multi-review findings on the inline subtask input:

- The open request was held in a signal that was never cleared, so a task row re-created with the same id (e.g. navigating away from a project and back) re-ran its open effect on init and re-opened the input, stealing focus with no user action. Reset via consume() once the row acts on it.

- Drop the redundant requestId counter (a fresh request value already re-fires the effect); request payload is now just the parentId string.

- Add an aria-label to the input (placeholder is not an accessible name).

- _commit() returns void (its boolean result was unused).

* feat(tasks): animate the inline subtask input out and tighten its top gap

- Use the expandFade animation (enter + leave) so the input collapses out instead of vanishing. Caveat: when the input is the only thing in .sub-tasks (adding the first subtask, then cancelling without creating any), the enclosing @if collapses in the same tick and Angular skips the child leave animation, so it's instant in that one case.

- Reduce the input's top margin from --s-half (4px) to --s-quarter (2px).

* feat(tasks): refocus the task row when the subtask draft is cancelled with Escape

The inline subtask input's 'closed' output now reports why it closed ('escape' vs 'blur'). On Escape (a keyboard cancel) the host task calls focusSelf() so keyboard navigation continues from that row; on blur it doesn't, since focus already moved elsewhere. focusSelf() is a no-op on touch.

Covered by unit tests (reason emitted, host refocus only on escape) and an e2e assertion that the task row is focused after Escape.

* feat(tasks): return focus to the task the subtask draft was opened from

Escape previously refocused the parent row that hosts the input. Capture the originating task (which may be a subtask) when opening the draft — before focus moves into the input and the parent row claims focusedTaskId — and restore that on Escape. Falls back to the host row when no origin was captured; no-op on touch.

Focus-by-id prefers the last #t-<id> instance (the detail side-panel one) to match the existing inline-edit focus convention when a task renders twice.

Covered by unit tests and an e2e proving focus returns to the originating subtask, not its parent.

* test(tasks): update subtask e2e for the inline draft input flow

The add-subtask UX changed from 'press a -> empty subtask title focused for edit' to an inline draft input (type + Enter to commit, stays open for rapid entry). Update every e2e site that added subtasks via the old textarea selector:

- WorkViewPage.addSubTask helper: wait for .e2e-add-subtask-input, fill + Enter, then Escape to close the draft for a clean post-state (fixes simple-subtask, add-to-today, drag-task-into-subtask, finish-day-quick-history, and the sync specs that use the helper).

- add-subtask-with-detail-panel-open.spec.ts: assert the draft input opens focused from panel/main-list focus; rewrite the multi-subtask case to repeated Enter (the new rapid-entry path).

- supersync-archive-subtasks + supersync-lww-conflict: same inline-draft flow (not runnable in-sandbox; fixed by analogy).

Verified locally: detail-panel-open 3/3, simple-subtask, add-to-today 5/5, drag-into-subtask, finish-day-quick-history all green.

---------

Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
2026-06-18 17:58:45 +02:00
..
scripts refactor(automationPlugin): move and rename 2025-12-02 13:30:37 +01:00
src fix(plugin-automations): guard nullish currentTaskChange payload 2026-05-18 21:59:25 +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 Codex/issue 8081 keep subtask input open (#8423) 2026-06-18 17:58:45 +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.