* 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>
* chore(deps): bump plugin-dev dev tooling to patch security alerts
Update vite, vitest, postcss, glob, and minimatch across the
packages/plugin-dev/* sample plugins to clear 26 Dependabot alerts.
These are dev/build tooling for the example plugins and are not
shipped to end users.
- vite >=7.3.2, vitest 4.1.x, postcss 8.5.15, glob 10.5.0,
minimatch 9.0.9/5.1.9 (all within declared semver ranges)
- clickup-issue-provider: vitest ^3.2.1 -> ^4.1.0 (only manifest change)
Verified test suites pass: clickup (28), automations (109).
* chore(deps): bump minimatch + webpack-dev-server overrides for security
Resolve the remaining root-lockfile Dependabot alerts in build/dev
tooling (not shipped to end users):
- minimatch: the existing `app-builder-lib` override pinned minimatch to
10.1.1, which npm cascades across electron-builder's entire subtree
(@electron/asar, @electron/universal, dir-compare, filelist, temp).
Bump the override to 10.2.5 (within app-builder-lib's ^10.0.3); the
vulnerable nested copies dedupe to the patched top-level. Clears
Dependabot #341, #372, #373 (minimatch ReDoS).
- webpack-dev-server: add override to 5.2.4. Clears #593.
Note: electron-builder `npm run dist` packaging not run in this env;
the minimatch bump is a patch within range so impact is expected nil.
The currentTaskChange handler destructured { current, previous } from
the hook payload with no nullish guard, unlike its sibling handlers
(taskCreated/taskComplete/taskUpdate) which all check payload first.
A nullish payload threw TypeError into PluginHooksService.dispatchHook,
logged as a plugin hook handler error. Guard the payload before
destructuring, matching the existing handler pattern.
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.
packages/vite-plugin/package.json was bumped to vite ^6.4.2 in
4b9bd23e56 but dependent plugin lockfiles still referenced ^6.0.0.
npm install during the e2e build brought them in sync and hoisted
vitest's nested esbuild binaries, dropping ~500 redundant entries.
- Separate JsonParseError and SyncDataCorruptedError handlers in sync wrapper
- Handle corrupted remote data gracefully instead of throwing
- Add getOrGenerateClientId() as unified entry point, eliminating dual injection
of ClientIdService + CLIENT_ID_PROVIDER in snapshot-upload, file-based-encryption,
and sync-hydration services
- Use crypto.getRandomValues() instead of Math.random() for client ID generation
- Warn user when stored client ID is invalid and must be regenerated
- Fix flaky e2e supersync tests (premature waitForURL match on daily-summary URL)
When a full-state op (SYNC_IMPORT/BACKUP_IMPORT) arrived from another
client, mergeRemoteOpClocks reset the local clock to a "minimal" form
but only preserved the current client's counter from the incoming op's
clock. If the current client had issued ops (e.g. GLOBAL_CONFIG) not
reflected in the incoming full-state op's clock, its counter was dropped,
causing subsequent ops to reuse the same counter value. Downstream clients
then saw these ops as EQUAL (duplicate) and skipped them silently.
Fix: take max(mergedClock[clientId], currentClock[clientId]) when
rebuilding the clock after a full-state op reset.
Also add __SP_E2E_BLOCK_WS_DOWNLOAD flag to WsTriggeredDownloadService
to allow E2E tests to block automatic WS-triggered downloads during
concurrent conflict scenarios.
Fix archive conflict test by blocking WS downloads on Client A during
the concurrent edit phase so it doesn't auto-receive B's rename via
WebSocket before archiving (restoring the intended conflict scenario).
Fix LWW singleton test to assert convergence rather than specific winner.
Fix renameTask helper to avoid Playwright/Angular re-render races.
Fix shepherd.js import paths that broke the Angular dev server build.
* fix(plugin-bridge): enhance task update logic and validation for project movement
* fix(plugin-bridge): include changes in task update event payload
* feat(RuleEditor): add tests for rule editing functionality and enhance action handling
* test(RuleEditor): add comprehensive tests for rule editing and condition handling
* feat(RuleRegistry): enhance rule validation and add support for advanced conditions and actions
* feat(types): extend condition and action types for enhanced automation capabilities
* feat(conditions): add regex support for title conditions and enhance checks
* feat(actions): add ActionMoveToProject to move tasks between projects
* feat(ActionDialog, ActionInput): add support for moveToProject action and enhance input handling
* feat(ConditionDialog, ConditionInput): add support for titleStartsWith and weekdayIs conditions, enhance regex handling
* feat(styles): add new input-with-toggle and field-error styles for improved layout and error handling
* feat(automation): add titleStartsWith condition and enhance task event handling
* feat(automations): add delete task action
add a new `deleteTask` action to the automations plugin
- register the action in the automation runtime
- expose it in the rule editor UI
- restrict it to task-based triggers
- validate and persist rules using `deleteTask`
- add focused tests for runtime behavior, validation, persistence, and UI
* feat(automations): remove false trigger workaround
remove the temporary taskCreated fallback after confirming the
reported trigger bug was a mistaken assumption
* fix(automations): address PR review feedback
- Add regex pattern length cap (200 chars) to mitigate ReDoS risk
- Use project ID instead of title as option value to prevent duplicate name collisions
- Replace dynamic import('rxjs') with static import for firstValueFrom
- Replace any[] with proper types for projects/tags props
- Replace changes?: any with Record<string, unknown> in TaskEvent
- Use createMemo + <Show> for regexError to avoid double reactive computation
- Remove noisy/inconsistent debug logging from automation-manager
- Remove verbose intermediate log from moveToProject action
- Clean up mock: remove unused moveTaskToProject, restore PluginAPI type
- Prefer ID lookup over title in ActionMoveToProject
* fix(automations): harden regex, use IDs for conditions, add validation and tests
- Add dangerous-pattern heuristic to reject nested quantifiers (e.g. (a+)+$)
that cause catastrophic backtracking, supplementing the length cap
- Switch projectIs/hasTag condition dropdowns to store IDs instead of titles
to survive project/tag renames (with title fallback for backward compat)
- Disable ActionDialog Save button when value is empty (except deleteTask)
- Fix webhook test to actually validate payload sanitization
- Add test for HTML escaping in ActionDisplayDialog
- Add tests for regex length cap and dangerous-pattern rejection
- Add tests for ConditionWeekdayIs (7 test cases covering full names,
abbreviations, comma-separated lists, case insensitivity, edge cases)
* test(automations): build dangerous regex pattern dynamically to avoid CodeQL flag
The test intentionally uses a catastrophic-backtracking pattern to verify
our safety heuristic rejects it. Build it via string concatenation so
CodeQL's static analysis doesn't flag the test itself.
* fix(automations): addTag lookup by ID, widen ReDoS heuristic to catch {n,}
- ActionAddTag now looks up tags by ID first (with title fallback),
consistent with all other condition/action lookups
- Extend DANGEROUS_REGEX_PATTERN to also detect {n,} quantifiers
inside nested groups (e.g. (a{2,})+) which also cause backtracking
---------
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
Bumps the npm_and_yarn group with 1 update in the /packages/plugin-dev/automations directory: [flatted](https://github.com/WebReflection/flatted).
Bumps the npm_and_yarn group with 1 update in the /packages/plugin-dev/boilerplate-solid-js directory: [flatted](https://github.com/WebReflection/flatted).
Updates `flatted` from 3.3.3 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)
Updates `flatted` from 3.3.3 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)
---
updated-dependencies:
- dependency-name: flatted
dependency-version: 3.4.2
dependency-type: indirect
dependency-group: npm_and_yarn
- dependency-name: flatted
dependency-version: 3.4.2
dependency-type: indirect
dependency-group: npm_and_yarn
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Tasks arriving via sync/import can have undefined titles, causing
task.title.toLowerCase() to throw a TypeError in the schedule view
and other search/filter components. Add optional chaining at all
vulnerable call sites.
Closes#6863
* 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
<head> 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.