From 4d22a649551d6cd102bd95a75ee5f39eb499708d Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Wed, 4 Feb 2026 18:18:22 +0100 Subject: [PATCH] Feat/plugin UI kit (#6362) * fix(e2e): stabilize undo task delete sync test Two flakiness sources fixed: - Click on task element could activate title inline editor, causing Backspace to edit text instead of triggering delete. Now clicks the drag handle which calls focusSelf() without entering edit mode. - Replaced deleteTask helper with inline sequence to avoid wasting 2s of the 5s undo snackbar window on dialog-detection timeout. * refactor: address code review findings from 2026-02-03 - Extract getBreakCycle helper to replace error-prone `cycle - 1 || 1` pattern at 3 call sites - Add clarifying comment on intentionally broad 'timed out' match - Reduce Pomodoro E2E test from 9 to 5 sessions (sufficient coverage) - Remove dead _isTransientNetworkError wrapper from DropboxApi - Extract stubWindowConfirm helper in task reducer tests * fix(sync): prevent Formly from clearing provider config on show (#6345) resetOnHide: true caused Formly to reset field values when provider fieldGroups transitioned from hidden to visible, discarding user input if sync was enabled before selecting a provider. * fix(tasks): fix huge space between emoji and text in tag/project menus Use matMenuItemIcon attribute on emoji spans so they project into the icon slot of mat-menu-item instead of the text slot. Update emoji icon sizing to 24x24px to match mat-icon and add overflow: hidden. Closes #5977 * fix(tasks): guard against undefined task entities in selectors and archive (#6359) Prevent TypeError crashes (reading 'dueWithTime', 'dueDay', 'issueProviderId') caused by orphaned IDs in NgRx state. Fix archive merge to deduplicate IDs, filter orphans, and use correct entity precedence (young over old). Add defensive null guards to selectors and archive/task service methods. * fix(tasks): guard against undefined task in mainListTasksInProject$ (#6360) * fix(tasks): detect and sanitize orphaned task IDs to prevent startup crashes (#6359, #6360) Orphaned task IDs (entries in task.ids without matching entities) caused TypeError on app startup. Fix addresses three layers: validation now flags orphaned IDs instead of silently skipping them, loadAllData sanitizes IDs on load as a safety net, and data repair no longer crashes when encountering orphaned IDs it's trying to fix. * fix(sync): prevent recurring task duplication across clients Remove SuperSync special-case that bypassed initial sync wait, causing repeatable task effects to fire before sync completed. Add post-sync cleanup effect that detects and removes stale duplicate repeat instances when multiple active instances exist for the same repeat config. * fix(sync): restore WebDAV provider compatibility warning text * feat(sync): mark WebDAV and LocalFile sync options as experimental * feat(plugins): add UI Kit with inject-first CSS strategy for iframe plugins Introduce a lightweight CSS reset (UI Kit) that auto-styles basic HTML elements in plugin iframes to match the host app theme. Injected after so plugin styles always win by source order. UI Kit provides: element resets (body, headings, buttons, inputs, tables, links, code, lists, hr), .btn-primary/.btn-outline button variants, and .card/.card-clickable components. All bundled plugins updated to use UI Kit classes, removing redundant custom CSS (-542 lines net). Pico CSS removed from automations plugin. sync-md converted from hardcoded colors to host theme variables. * feat(plugins): extract shared CSS utilities into UI Kit Move .text-muted, .text-primary, .page-fade and @keyframes fadeIn from plugin CSS into the UI Kit so all iframe plugins get them automatically. Add box-shadow focus ring to input:focus for better accessibility. Remove per-plugin focus overrides now covered by the UI Kit. --- docs/ai/plugin-ui-consistency-plan.md | 5 + docs/plugin-development.md | 102 +++-- docs/wiki/3.06-User-Data.md | 20 +- packages/plugin-api/README.md | 1 + packages/plugin-api/src/types.ts | 1 + .../ai-productivity-prompts/src/App.css | 403 ++++-------------- .../src/ProcrastinationInfo.tsx | 2 +- .../src/components/CustomPromptEditor.tsx | 4 +- .../src/components/PromptView.tsx | 2 +- .../src/components/shared/Button.tsx | 18 +- .../plugin-dev/api-test-plugin/index.html | 378 ++++++++-------- .../plugin-dev/automations/src/app/App.css | 179 +++----- .../plugin-dev/automations/src/app/App.tsx | 20 +- .../src/app/components/ActionDialog.tsx | 12 +- .../src/app/components/ConditionDialog.tsx | 6 +- .../src/app/components/RuleEditor.tsx | 25 +- .../src/app/components/RuleList.tsx | 18 +- .../plugin-dev/automations/src/index.html | 16 - packages/plugin-dev/automations/src/index.tsx | 1 - .../procrastination-buster/src/App.css | 189 +------- .../procrastination-buster/src/App.tsx | 6 +- .../src/ProcrastinationInfo.tsx | 2 +- packages/plugin-dev/sync-md/src/ui/index.html | 194 ++------- .../yesterday-tasks-plugin/index.html | 48 +-- src/app/plugins/util/plugin-iframe.util.ts | 26 +- src/app/plugins/util/plugin-ui-kit.css.ts | 189 ++++++++ 26 files changed, 747 insertions(+), 1120 deletions(-) create mode 100644 src/app/plugins/util/plugin-ui-kit.css.ts diff --git a/docs/ai/plugin-ui-consistency-plan.md b/docs/ai/plugin-ui-consistency-plan.md index 2e9a9251a3..73f5382265 100644 --- a/docs/ai/plugin-ui-consistency-plan.md +++ b/docs/ai/plugin-ui-consistency-plan.md @@ -1,5 +1,10 @@ # Plan: Plugin UI Consistency via CSS Library + Reactive Theme +## Status + +- **Part 2 (CSS Component Library)**: Implemented as a UI Kit element reset in `src/app/plugins/util/plugin-ui-kit.css.ts`. Uses plain element selectors injected at the start of `` (inject-first), so plugin CSS always wins by source order. No class-based components — just styled HTML elements. +- **Part 1 (Reactive Theme Updates / THEME_CHANGE hook)**: Not yet implemented. + ## Goal Make iframe plugin UI more consistent with the main app by: diff --git a/docs/plugin-development.md b/docs/plugin-development.md index 3b7bbe04a3..9779645263 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -85,21 +85,22 @@ The `manifest.json` file is required for all plugins and defines the plugin's me ### Manifest Fields -| Field | Type | Required | Description | -| ----------------- | -------- | -------- | ------------------------------------------------------------------ | -| `id` | string | ✓ | Unique identifier for your plugin (use kebab-case) | -| `name` | string | ✓ | Display name shown to users | -| `version` | string | ✓ | Semantic version (e.g., "1.0.0") | -| `description` | string | ✓ | Brief description of what your plugin does | -| `manifestVersion` | number | ✓ | Currently must be `1` | -| `minSupVersion` | string | ✓ | Minimum Super Productivity version required | -| `author` | string | | Plugin author name | -| `homepage` | string | | Plugin website or repository URL | -| `icon` | string | | Path to icon file (SVG recommended) | -| `iFrame` | boolean | | Whether plugin uses iframe UI (default: false) | -| `sidePanel` | boolean | | Show plugin in side panel (default: false), requires `iFrame:true` | -| `permissions` | string[] | | The permissions the plugin needs (e.g., ["nodeExecution"]) | -| `hooks` | string[] | | App events to listen to | +| Field | Type | Required | Description | +| ----------------- | -------- | -------- | -------------------------------------------------------------------------------------- | +| `id` | string | ✓ | Unique identifier for your plugin (use kebab-case) | +| `name` | string | ✓ | Display name shown to users | +| `version` | string | ✓ | Semantic version (e.g., "1.0.0") | +| `description` | string | ✓ | Brief description of what your plugin does | +| `manifestVersion` | number | ✓ | Currently must be `1` | +| `minSupVersion` | string | ✓ | Minimum Super Productivity version required | +| `author` | string | | Plugin author name | +| `homepage` | string | | Plugin website or repository URL | +| `icon` | string | | Path to icon file (SVG recommended) | +| `iFrame` | boolean | | Whether plugin uses iframe UI (default: false) | +| `sidePanel` | boolean | | Show plugin in side panel (default: false), requires `iFrame:true` | +| `permissions` | string[] | | The permissions the plugin needs (e.g., ["nodeExecution"]) | +| `hooks` | string[] | | App events to listen to | +| `uiKit` | boolean | | Enable UI Kit CSS reset for iframe plugins (default: true). Set to `false` to disable. | ### Complete Manifest Example @@ -172,37 +173,22 @@ Plugins that render custom UI in a sandboxed iframe. My Plugin UI - + @@ -249,6 +235,46 @@ Plugins that render custom UI in a sandboxed iframe. ``` +### Theme Variables & UI Kit + +Iframe plugins automatically receive: + +1. **CSS variables** — All theme variables (colors, spacing, shadows, transitions) are injected as CSS custom properties on `:root`. Use `var(--c-primary)`, `var(--bg)`, `var(--text-color)`, etc. + +2. **UI Kit CSS reset** — By default, basic HTML elements (`button`, `input`, `select`, `textarea`, `table`, `a`, `h1`–`h6`, `p`, `code`, `pre`, `hr`, etc.) are styled to match the app's look. This is injected before your plugin's own styles, so your CSS always wins. + + To disable the UI Kit, add `"uiKit": false` to your manifest. + +**Button variants:** + +- Default ` void; variant?: 'primary' | 'secondary' | 'back'; - size?: 'small' | 'medium' | 'large'; disabled?: boolean; title?: string; class?: string; @@ -12,18 +11,15 @@ interface ButtonProps { export const Button: Component = (props) => { const getButtonClass = () => { - const baseClass = 'button'; - const variantClass = props.variant - ? `${baseClass}-${props.variant}` - : 'action-button primary'; - const sizeClass = props.size ? `${baseClass}-${props.size}` : ''; const customClass = props.class || ''; - - if (props.variant === 'back') { - return 'back-button'; + switch (props.variant) { + case 'back': + case 'secondary': + return `btn-outline ${customClass}`.trim(); + case 'primary': + default: + return `btn-primary ${customClass}`.trim(); } - - return `${variantClass} ${sizeClass} ${customClass}`.trim(); }; return ( diff --git a/packages/plugin-dev/api-test-plugin/index.html b/packages/plugin-dev/api-test-plugin/index.html index 21cd820dae..355ff3c3f5 100644 --- a/packages/plugin-dev/api-test-plugin/index.html +++ b/packages/plugin-dev/api-test-plugin/index.html @@ -8,214 +8,230 @@ /> API Test Plugin Dashboard

API Test Plugin

-
-

Available API Methods

-
-
getTasks()
-
getArchivedTasks()
-
getCurrentContextTasks()
-
addTask(data)
-
updateTask(id, data)
-
getAllProjects()
-
addProject(data)
-
updateProject(id, data)
-
getAllTags()
-
addTag(data)
-
updateTag(id, data)
-
showSnack(config)
-
notify(config)
-
openDialog(config)
-
persistDataSynced(data)
-
loadSyncedData()
-
registerHook(hook, handler)
-
registerHeaderButton(config)
-
registerMenuEntry(config)
-
registerShortcut(config)
-
setCounter(key, value)
-
getCounter(key)
-
incrementCounter(key, amount)
-
decrementCounter(key, amount)
-
deleteCounter(key)
-
getAllCounters()
-
+

Available API Methods

+

+ getTasks() + getArchivedTasks() + getCurrentContextTasks() + addTask(data) + updateTask(id, data) + getAllProjects() + addProject(data) + updateProject(id, data) + getAllTags() + addTag(data) + updateTag(id, data) + showSnack(config) + notify(config) + openDialog(config) + persistDataSynced(data) + loadSyncedData() + registerHook(hook, handler) + registerHeaderButton(config) + registerMenuEntry(config) + registerShortcut(config) + setCounter(key, value) + getCounter(key) + incrementCounter(key, amount) + decrementCounter(key, amount) + deleteCounter(key) + getAllCounters() +

+
+ + + +
+ +

Data Persistence Tests

+
+ +
- - -
-

Data Persistence Tests

-
- - -
+

Task Operations

+
+ + + + +
-
-

Task Operations

-
- - - - - -
+

Project & Tag Operations

+
+ + + +
-
-

Project & Tag Operations

-
- - - - -
+

UI Operations

+
+ + + + +
-
-

UI Operations

-
- - - - - -
+

Advanced Dialog Examples

+
+ +
-
-

Advanced Dialog Examples

-
- - -
-
- -
-
Console output will appear here...
+ >Console output will appear here... + +
+ +

UI Kit Component Showcase

+ +

Headings

+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+ +

Text

+

+ This is a paragraph with bold, italic, and + link styles. +

+

Inline code is styled automatically.

+ +

Buttons

+
+ + + + + +
+

Inputs

+ + + + + +

Code Block

+
const tasks = await PluginAPI.getTasks();
+console.log(tasks.length);
+ +

Table

+ + + + + + + + + + + + + + + + + + + + + +
MethodDescription
getTasks()Get all active tasks
addTask(data)Create a new task
showSnack(cfg)Show a notification
+ +

Lists

+
    +
  • Unordered list item 1
  • +
  • Unordered list item 2
  • +
  • Unordered list item 3
  • +
+
    +
  1. Ordered list item 1
  2. +
  3. Ordered list item 2
  4. +
  5. Ordered list item 3
  6. +
+ +

Cards

+
+

Default Card

+

A static card with background, shadow, and border.

+
+
+
+

Clickable Card

+

Hover to see the lift effect and primary border highlight.

+
+ +

Horizontal Rule

+
+