* 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>
|
||
|---|---|---|
| .. | ||
| ai-productivity-prompts | ||
| api-test-plugin | ||
| automations | ||
| boilerplate-solid-js | ||
| brain-dump | ||
| caldav-calendar-provider | ||
| clickup-issue-provider | ||
| doc-mode | ||
| gitea-issue-provider | ||
| github-issue-provider | ||
| google-calendar-provider | ||
| linear-issue-provider | ||
| procrastination-buster | ||
| scripts | ||
| sync-md | ||
| trello-issue-provider | ||
| voice-reminder | ||
| yesterday-tasks-plugin | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| PLUGIN_I18N.md | ||
| QUICK_START.md | ||
| README.md | ||
Super Productivity Plugin Development
This directory contains tools and examples for developing plugins for Super Productivity.
Quick Commands
# Build all plugins
npm run build
# Install dependencies for all plugins
npm run install:all
# Clean build artifacts
npm run clean:dist
# List available plugins
npm run list
Getting Started
Prerequisites
- Node.js 18 or higher
- npm or yarn
- TypeScript knowledge (recommended)
Quick Start
-
Copy the example plugin:
cp -r example-plugin my-plugin cd my-plugin -
Install dependencies:
npm install -
Update plugin metadata:
- Edit
manifest.jsonwith your plugin details - Update
package.jsonwith your plugin name and description
- Edit
-
Start development:
npm run dev -
Build for production:
npm run build
Project Structure
my-plugin/
├── package.json # NPM package configuration
├── tsconfig.json # TypeScript configuration
├── webpack.config.js # Build configuration
├── manifest.json # Plugin manifest (metadata)
├── src/
│ └── index.ts # Main plugin code
├── assets/
│ ├── index.html # Optional UI (for iframe plugins)
│ └── icon.svg # Plugin icon
├── scripts/
│ └── package.js # Script to create plugin.zip
└── dist/ # Build output
├── plugin.js # Compiled plugin code (optional for iframe-only plugins)
├── manifest.json # Copied manifest
└── plugin.zip # Packaged plugin
Development Workflow
1. Local Development
For rapid development within the Super Productivity repo:
# Build and install to local Super Productivity
npm run install-local
# This copies your built plugin to:
# ../../../src/assets/my-plugin/
Then run Super Productivity in development mode to test your plugin.
2. Watch Mode
Keep the plugin building automatically as you make changes:
npm run dev
3. Type Checking
Ensure your code is type-safe:
npm run typecheck
4. Linting
Check code quality:
npm run lint
Plugin API
The plugin receives a global PluginAPI object with these capabilities:
Configuration
cfg- Current app configuration (theme, platform, version)
UI Integration
registerMenuEntry()- Add menu itemsregisterHeaderButton()- Add header buttonsregisterSidePanelButton()- Add side panel buttonsregisterShortcut()- Register keyboard shortcutsshowIndexHtmlAsView()- Display plugin UI
Data Access
getTasks()- Get all tasksgetArchivedTasks()- Get archived tasksgetCurrentContextTasks()- Get current project/tag tasksupdateTask()- Update a taskaddTask()- Create new taskgetAllProjects()- Get all projectsgetAllTags()- Get all tags
User Interaction
showSnack()- Display snack bar notificationsnotify()- Show system notificationsopenDialog()- Open custom dialogs
Data Persistence
persistDataSynced()- Save plugin dataloadSyncedData()- Load saved data
Internationalization (i18n)
translate(key, params?)- Get translated textformatDate(date, format)- Format dates with localegetCurrentLanguage()- Get current language code
See PLUGIN_I18N.md for the complete i18n guide.
Hooks
Register handlers for lifecycle events:
taskComplete- Task marked as donetaskUpdate- Task modifiedtaskDelete- Task removedcurrentTaskChange- Active task changedlanguageChange- App language changedfinishDay- End of day
Example Usage
// Register a task complete handler
PluginAPI.registerHook('taskComplete', async (task) => {
console.log('Task completed:', task);
PluginAPI.showSnack({
msg: `Great job completing: ${task.title}`,
type: 'SUCCESS',
});
});
// Add a keyboard shortcut
PluginAPI.registerShortcut({
id: 'my-action',
label: 'My Plugin Action',
onExec: async () => {
const tasks = await PluginAPI.getTasks();
console.log(`You have ${tasks.length} tasks`);
},
});
// Use translations (if plugin has i18n support)
const greeting = PluginAPI.translate('MESSAGES.GREETING');
const taskCount = PluginAPI.translate('TASK_COUNT', { count: tasks.length });
const dueDate = PluginAPI.formatDate(task.dueDate, 'short');
Building for Distribution
1. Create Plugin Package
npm run build
npm run package
This creates dist/plugin.zip ready for distribution.
2. File Size Limits
- Plugin ZIP: 50MB maximum
- Plugin code (plugin.js): 10MB maximum
- Manifest: 100KB maximum
- index.html: 100KB maximum
3. Required Files
Your plugin ZIP must contain:
manifest.json- Plugin metadataplugin.js- Main plugin code, unless this is an iframe-only plugin withiFrame: trueandindex.html
Optional files:
index.html- UI for iframe pluginsicon.svg- Plugin iconi18n/*.json- Translation files for multi-language support
Publishing Your Plugin
GitHub Release (Recommended)
- Create a GitHub repository for your plugin
- Use GitHub Actions to build releases:
name: Build Plugin
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- run: npm run package
- uses: softprops/action-gh-release@v1
with:
files: dist/plugin.zip
- Users can download the
.zipfile from your releases
NPM Package
You can also publish your plugin source to npm:
- Update
package.jsonwith your npm scope - Build your plugin:
npm run build - Publish:
npm publish
Users would need to build it themselves or you can include the built files.
Testing Your Plugin
1. In Development Mode
# Build your plugin
npm run build
# Copy to Super Productivity assets
npm run install-local
# Run Super Productivity in dev mode
cd ../../.. && npm start
2. In Production Build
- Build your plugin:
npm run package - Open Super Productivity
- Go to Settings → Plugins
- Click "Upload Plugin"
- Select your
plugin.zipfile
3. Debugging
- Open browser DevTools to see console logs
- Check the Console for plugin errors
- Use
console.log()in your plugin code - The plugin runs in the main window context
TypeScript Development
Benefits
- Type Safety: Full IntelliSense and compile-time checking
- API Discovery: Auto-complete for all PluginAPI methods
- Refactoring: Safe code refactoring with TypeScript
- Documentation: Inline documentation in your IDE
Example with Types
import type { TaskData, ProjectData } from '@super-productivity/plugin-api';
// Type-safe task handling
async function processTask(task: TaskData): Promise<void> {
if (task.projectId) {
const projects = await PluginAPI.getAllProjects();
const project = projects.find((p) => p.id === task.projectId);
if (project) {
console.log(`Task "${task.title}" belongs to project "${project.title}"`);
}
}
}
// Type-safe hook registration
PluginAPI.registerHook('taskUpdate', (data: unknown) => {
const task = data as TaskData;
processTask(task);
});
Best Practices
- Error Handling: Always wrap async operations in try-catch
- Performance: Don't block the main thread with heavy computations
- State Management: Use
persistDataSynced()for plugin state - User Experience: Provide clear feedback with snack messages
- Permissions: Only request permissions you actually need
- Version Compatibility: Set appropriate
minSupVersion - Internationalization: Add i18n support to reach more users (see PLUGIN_I18N.md)
Troubleshooting
Plugin not loading
- Check browser console for errors
- Verify manifest.json is valid JSON
- Ensure all required fields are present
- Check file size limits
TypeScript errors
- Run
npm run typecheckto see all errors - Ensure
@super-productivity/plugin-apiis installed - Check tsconfig.json settings
Build issues
- Delete
dist/and rebuild - Check webpack.config.js for errors
- Ensure all dependencies are installed
Examples
Available Examples
- minimal-plugin - The simplest possible plugin (10 lines)
- simple-typescript-plugin - TypeScript with minimal tooling
- example-plugin - Full featured example with webpack
- boilerplate-solid-js - Modern Solid.js boilerplate with i18n support
- procrastination-buster - SolidJS plugin with modern UI
Example Features
boilerplate-solid-js demonstrates:
- SolidJS for reactive UI
- Vite for fast builds
- Internationalization (i18n) support with example translations
- Modern component architecture
- Plugin-to-iframe communication
- Best practices for plugin development
example-plugin demonstrates:
- TypeScript setup with webpack
- All API methods
- iframe UI integration
- State persistence
- Hook handling
- Build configuration
procrastination-buster demonstrates:
- SolidJS for reactive UI
- Vite for fast builds
- Modern component architecture
- Plugin-to-iframe communication
- Real-world use case
Support
- GitHub Issues: Super Productivity Issues
- Plugin API Docs: See
packages/plugin-api/README.md