Commit graph

20918 commits

Author SHA1 Message Date
Johannes Millan
86357f705a fix(pwa): cache the hashed icon font so it renders offline on iOS #8138
In production the esbuild builder fingerprints the @font-face url() and
emits the Material Symbols woff2 to /media/<hash>.woff2, but ngsw-config
only precached the unhashed /assets/fonts copy that nothing loads. The
font the app actually requests was therefore never in the service worker
cache and failed to load offline, leaving icons as their ligature names
(wb_sunny, settings, ...). iOS Safari surfaces this first because, unlike
Chrome, it does not fall back to the HTTP disk cache offline.

- prefetch /media/material-symbols-outlined-*.woff2 (the URL @font-face
  actually requests)
- lazily cache the rest of /media (self-hosted Open Sans) so offline text
  also keeps its font
- font-display: block so the brief load window shows blank glyph space
  rather than the ligature names
2026-06-08 18:29:55 +02:00
Johannes Millan
e9ceb841d6 docs: flag task component (perf) and sync system (correctness) as high-risk areas 2026-06-08 18:29:55 +02:00
Johannes Millan
3ff0eebf92
fix(sync): make 'Use Server Data' recoverable + guard the destructive choice (#8107) (#8151)
* fix(sync): back up local state before USE_REMOTE replace #8107

forceDownloadRemoteState cleared all unsynced local ops and replaced
NgRx state with the server snapshot with no recovery point, making an
unintended 'Use Server Data' conflict choice irreversibly destructive.

Capture a pre-wipe snapshot via the existing single-slot IMPORT_BACKUP
store (BackupService.captureImportBackup) before clearUnsyncedOps, and
abort the replace if the backup fails. Offer a 30s Undo snack afterwards
that restores it (BackupService.restoreImportBackup). Covers both the
conflict-dialog USE_REMOTE path and the manual force-download.

* fix(sync): durable restore entry + one-shot recovery for USE_REMOTE backup #8107

Addresses multi-review findings on the pre-wipe backup:

- Add a persistent 'Restore data from before last sync replace' button to
  the sync settings (all providers, gated on a remaining backup), so
  recovery survives a missed/replaced Undo snack or a failure that aborts
  after the wipe — the snack was previously the only reader of the backup.
- Make restore one-shot: consume the IMPORT_BACKUP slot after a successful
  restore, preventing a second restore from toggling back to the replaced
  state and avoiding a lingering plaintext snapshot.
- Undo snack: WARNING type (honest framing + dismiss control) and no
  auto-dismiss timer instead of SUCCESS/30s.

* test(sync): e2e for USE_REMOTE pre-replace backup + undo #8107

WebDAV two-client conflict: Client B's local task is wiped by 'Keep
remote' (USE_REMOTE), then the Undo snack restores it. Reproduces the
#8107 data loss and verifies recovery. Modeled on the existing
webdav-first-sync-conflict USE_REMOTE test.

Runnable via npm run e2e:webdav:file (needs the docker WebDAV server;
not runnable in the sandbox where published ports are unreachable).

* fix(sync): guard USE_REMOTE in conflict dialog; drop durable restore button #8107

Swap the over-built recovery surface for a root-cause fix:

- Add a confirmation guard before 'Use Server Data' (USE_REMOTE) discards
  pending local changes (INCOMING_IMPORT with local ops), mirroring the
  existing USE_LOCAL guard. The dialog frames the server as 'recommended',
  so this stops a misclick from silently wiping data the user can't tell
  is newer than the server's — attacking the cause, not just recovery.
- Revert the durable settings restore button + its one-shot clear /
  hasImportBackup (beyond the minimal fix; only that button used them).

Keeps the minimal recovery net: pre-wipe capture + sticky WARNING undo
snack (and its passing WebDAV e2e).

* fix(sync): explain encryption-blocked restore instead of generic error #8107

Defect 2: server-side Restore-from-History can't replay end-to-end-
encrypted ops, so it rejects with a 4xx whose reason mentions encryption
(the provider embeds that reason in the thrown error message). The client
showed a meaningless 'Failed to restore data' (bbinet's screenshot).

Detect the encryption reason in the restore catch and show a dedicated
RESTORE_ENCRYPTED message explaining the limitation; falls back to the
generic message for any other failure.

* fix(sync): guard USE_REMOTE undo against superseded backup slot #8107

The pre-replace backup uses a single IndexedDB slot shared with the
backup-import flow. The Undo snack never expires, so an intervening
import or a second 'Use Server Data' could overwrite the slot before
the user clicks Undo, silently restoring the wrong snapshot.

Thread the backup's savedAt token from capture through the snack to
restore; restoreImportBackup() now refuses when the stored backup no
longer matches the token. Also clear the slot after a successful
restore so a full copy of the replaced state stops lingering in IDB
(uses the previously-uncalled clearImportBackup).
2026-06-08 16:34:59 +02:00
Johannes Millan
a8bccb9e31
fix(tasks): add subtask via "a" shortcut when detail panel is open (#8152)
* build: drop unused elevate.exe from Windows build #8135

elevate.exe is bundled by electron-builder's NSIS target solely so
electron-updater can apply privileged per-machine updates. We ship no
in-app auto-updater (electron-updater is not a dependency; the autoUpdater
block in electron/start-app.ts is commented out), so the binary is unused
dead weight and a frequent AV false-positive. Set packElevateHelper: false
to stop bundling it; safe because perMachine is not true.

* fix(tasks): add subtask via "a" shortcut when detail panel is open

With the detail panel open, pressing "a" created a sub-task but did not
focus it for editing.

Two paths were broken:
- Focus inside the panel: the panel auto-focuses a detail item on open, so
  focus is not on a <task> row and the global task-shortcut handler drops
  the shortcut. The panel now handles taskAddSubTask in its keydown listener
  (stopPropagation avoids a double-add when focus is on an in-panel row).
- Focus on the main-list task row: addSubTaskTo -> focusTaskById targeted the
  last #t-<id> copy, which is the in-panel copy inside the collapsed (hidden)
  sub-task section and cannot take focus. focusTaskById now falls back to the
  last focusable copy, so the new sub-task title is focused for editing
  (the panel follows focus to the new sub-task).
2026-06-08 16:21:45 +02:00
Johannes Millan
d19ec37aaa
fix(notifications): defer mobile permission prompt to first use (#8120) (#8137)
* fix(notifications): defer mobile permission prompt to first use (#8120)

The startup effect (askPermissionsIfNotGiven$) requested notification
permission ~2s after launch via ensurePermissions(), which contradicts
the documented lazy-prompt design in CapacitorReminderService.initialize()
and lowers iOS/Android grant rates (an unprompted launch-time dialog is
dismissed more often than a contextual one).

Read the permission STATE instead and only warn on explicit 'denied';
'prompt'/'prompt-with-rationale' now stay silent so the OS dialog surfaces
lazily on the first real schedule (when the user creates a reminder).

- add getPermissionState() to CapacitorNotificationService (no prompt) and
  refactor checkPermissions() to delegate to it
- add getPermissionState() to CapacitorReminderService ('granted' for the
  legacy Android WebView per #7408, 'denied' when unavailable)
- branch the startup effect on the tri-state and defer the exact-alarm
  check until notifications are granted

* fix(notifications): check exact alarms after lazy grant

* refactor(notifications): drop dead duplicate snack in exact-alarm catch

ensureExactAlarmPermission() never rejects (it swallows its own errors),
so the .catch() that re-opened the EXACT_ALARM_DENIED snack was an
unreachable duplicate of the .then() warning. Keep a resolving, log-only
catch so a future throw can't cache a rejected promise that every
scheduling effect would re-await, and document the one-shot memo and the
isAndroid() gate.
2026-06-08 16:06:16 +02:00
Johannes Millan
aa2ad88ff1
fix(caldav-plugin): make recurring-occurrence edits/deletes safe and quiet #7492 (#8149)
* build: drop unused elevate.exe from Windows build #8135

elevate.exe is bundled by electron-builder's NSIS target solely so
electron-updater can apply privileged per-machine updates. We ship no
in-app auto-updater (electron-updater is not a dependency; the autoUpdater
block in electron/start-app.ts is commented out), so the binary is unused
dead weight and a frequent AV false-positive. Set packElevateHelper: false
to stop bundling it; safe because perMachine is not true.

* fix(caldav-plugin): make recurring-occurrence edits/deletes safe and quiet #7492

Expanded RRULE occurrences all share one master .ics, so operating on a
single occurrence hit the whole series: updateIssue rewrote the master
(and the next poll pulled it onto every sibling), deleteIssue deleted the
master (the entire series), and getById returned the master's DTSTART,
collapsing every instance onto the first one's time.

Plugin:
- parseCompoundId surfaces occurrenceMs instead of discarding it
- getById re-anchors start/end onto the requested occurrence (timed via
  toIcalUtcDateTime, all-day via toIcalDate matching ical.js local-midnight)
- updateIssue/deleteIssue refuse occurrence writes with a marked error
  (isExpectedSyncSkip), so the series is never mutated

Host two-way sync:
- editing or deleting a task linked to one occurrence now stays silent (the
  user changed their task, not the calendar) and the local change still
  applies. Explicit agenda reschedule/delete keep surfacing the honest
  "can't change a single occurrence" message (no false success).

Single non-recurring events are unaffected. Full per-occurrence editing
(RECURRENCE-ID overrides + EXDATE) remains a follow-up; it needs an If-Match
primitive and recurrence-value preservation.
2026-06-08 16:05:52 +02:00
Johannes Millan
eff41c041d
feat(project): project completion experience (#8036)
* feat(project): add project completion with celebration + trophy view

Completing a project marks it done and archives it, with a celebration
dialog (confetti + live stats) and a prompt to resolve unfinished tasks
(move to Inbox / mark done). Completed projects show a trophy badge and
a Reopen action on the archived-projects page.

isDone stays distinct from isArchived; selectArchivedProjects is left
intact so completed projects' tasks stay filtered out of Today/Overdue.
Append/merge deferred to #8032. Plan: docs/plans/2026-06-05-project-completion.md

* refactor(project): drop Archive menu item; Complete is the retire path

Archive and Complete produced near-identical end states; collapse to one
user-facing action. Removes the 'Archive project' menu item and handler so
Complete is the single way to retire a project. The archiveProject action,
reducer and ProjectService.archive() stay (needed for op-log replay of
historical archive ops and the legacy unarchive/restore path). Wiki updated;
menu specs cover the Complete flow.

* fix(project): keep completion dialog open for the active project

MatDialog closeOnNavigation (default true) dismissed the celebration the
moment completing the currently-active project navigated to '/'. Navigate
first, then open the dialog.

* test(project): e2e for completion flow (complete, celebrate, reopen)

Covers the resolve-unfinished-tasks prompt, the celebration dialog with
stats, the trophy badge on the archived page, and reopening. Adds an
openProjectContextMenu page-object helper.

* feat(project): confirm project completion

* feat(project): show completion celebration fullscreen

* fix(project): harden completion celebration flow

* style(project): use spacing tokens in completion dialog

* fix(project): align completion screen with project context

* style(project): soften completion screen coloring

* style(project): reuse completion screen surfaces

* fix: refine project completion dialog actions

* fix: complete projects with atomic task resolution

* fix: restore archive path in project completion UI

* refactor(project): remove dead completion code

The project-level completeProject action (OpType.Update) was never
dispatched — completion goes exclusively through the atomic
TaskSharedActions.completeProject (OpType.Batch) meta-reducer. Drop the
dead action, its reducer case, the PROJECT_COMPLETE enum member and the
immutable 'PCO' op-log code (which would otherwise be permanently
reserved for an op that can never be produced); enum count 147->146.

Also remove the unused selectCompletedProjects / selectPlainArchivedProjects
selectors (no consumers) and the misspelled, unused 'angel' confetti
field, keeping the regression tests that guard selectArchivedProjects.

* fix(project): make project completion non-reversible

Completion resolves a project's open tasks (move-to-inbox / mark-done),
which reopen cannot truly restore, so the "Reopen"/"Undo" affordances on
the completion path were misleading. Drop the celebration dialog's Reopen
button and the post-complete undo snack; the fullscreen celebration is
the feedback and deliberate reactivation still lives on the
archived-projects page (Project.reopen kept for it).

Also restore the project title param on the archive confirm dialog (it
was rendering a raw {{title}} placeholder), remove the now-unused
moveTasksToInbox / markTasksDone resolution helpers (the meta-reducer
resolves tasks atomically), and drop the orphaned UNDO / S.COMPLETED
i18n keys. Updates the completion e2e to the close flow and asserts the
resolution props are forwarded to the atomic action.

* fix(tasks): cancel native reminders for project-completed tasks

Completing a project marks its unfinished tasks done inside the
meta-reducer (no per-task updateTask), so unscheduleDoneTask$'s
native-reminder cancellation is bypassed and an OS-scheduled Android
notification could still fire for a now-done task. Add a local-only
effect that cancels native reminders for the force-completed task ids.

Local-only by design: it dispatches no actions (the persistent
dismissReminderOnly/clearDeadlineReminder would each be an extra synced
op), and done tasks are already filtered from reminders$ on all
platforms — only the native Android notification needs explicit removal.

* test(project): drop TaskService spies orphaned by helper removal

getByIdWithSubTaskData$/moveToProject/setDone/setUnDone were only used
by the removed moveTasksToInbox/markTasksDone helpers and their deleted
tests.

* feat(sync): affectedEntities multi-entity conflict detection for atomic completion

WIP checkpoint of the atomic completeProject approach. The Batch op declares
every touched entity (PROJECT, INBOX, TASKs, TODAY_TAG) via a new
affectedEntities field threaded through op-log capture, conflict detection,
the sync server (+Prisma migration) and shared-schema. Per-effect
completeProject handlers (issue two-way-sync, time-block, repeat-cfg)
re-derive the task changes the atomic op bypasses.

* revert(sync): remove affectedEntities multi-entity conflict detection

Reverts 0893a86162. The affectedEntities feature existed solely to make the
atomic completeProject Batch op sync-correct (its only producer). Decoupling
project completion into normal per-task ops (next commit) makes the existing
per-entity conflict detection and effects fire naturally, so this entire
layer — sync-core, super-sync-server, shared-schema, op-log plumbing, the
Prisma migration, and the per-effect completeProject listeners — is no longer
needed. Preserved in history via the checkpoint commit.

* refactor(project): decouple completion from task resolution (Option C)

Completion was an atomic multi-entity Batch op (completeProject) that marked
tasks done / moved them to Inbox inside the project-shared meta-reducer.
Because it bypassed the normal per-task actions, every downstream consumer had
to be taught about it separately — conflict detection (the affectedEntities
feature, reverted in the previous commit), native-reminder cancellation, issue
two-way-sync, time-block and repeat-cfg effects.

Decouple instead: completion is now a plain single-entity PROJECT flag flip
(completeProject = OpType.Update, mirroring archiveProject). Unfinished-task
resolution runs first as the normal per-task actions (moveToOtherProject /
updateTask isDone) from the completion flow, so the existing effects and
per-entity conflict detection fire naturally — no special-casing anywhere.

- project.actions/reducer: restore plain completeProject action + on() handler
- project.service: complete() is a flag dispatch; restore moveTasksToInbox /
  markTasksDone (normal per-task dispatch + Rule #6 flush)
- work-context-menu: resolve unfinished work before the flag flip
- drop the completeProject meta-reducer block, the Batch action, the
  TASK_SHARED_COMPLETE_PROJECT op code, and the reminder-cancel effect
  (unscheduleDoneTask$ already cancels native reminders on the normal path);
  current-task clearing is covered by the existing task-internal effect

Net: ~190 LOC removed here on top of ~1565 (affectedEntities + a Prisma
migration) in the revert. Completion's task resolution is not undone either
way, so the atomic bundle never bought a clean reversal.

* docs(project): record decoupled-completion decision (ADR #5)

Document why project completion uses decoupled per-task resolution + a plain
single-entity flag flip instead of an atomic multi-entity op: the atomic op
forced a cross-stack affectedEntities conflict-detection feature and per-effect
listeners, for an undo guarantee it never delivered. Adds ARCHITECTURE-DECISIONS
#5 and a revision note + corrected undo/bulk-mechanic notes in the plan doc.

* test(project): pin completion ordering + resolution edge cases

Address multi-agent review of the decoupled-completion refactor:
- assert resolution (moveTasksToInbox / markTasksDone) runs BEFORE the
  completeProject flag flip (toHaveBeenCalledBefore) — the core invariant of
  the decoupled design that was previously not pinned
- cover the not-done branch of moveTasksToInbox (no setUnDone) and assert
  markTasksDone dispatches exactly the passed set
- add explicit PCO encode/decode round-trip assertions
- document the inbox-path current-task carry-forward nuance in ADR #5

Composition is covered end-to-end by e2e/tests/project/project-completion.spec.ts.

* refactor(project): tighten completion flow per review

- collapse 3x getCompletionInfo() to <=2: gate the resolve prompt once,
  recompute only after a resolution; each call now wrapped with an error
  snack so a failed archive load no longer aborts silently
- drop the dead post-confirm re-prompt branch (unreachable between two
  sequential single-user modals)
- reuse getDiffInDays + dateStrToUtcDate in completion-stats util instead
  of hand-rolled local-midnight/duration helpers
- use a Set for the top-level-task membership check (was O(n*m))
- drop redundant inline dialog sizing; the panelClass owns fullscreen
- remove dead --project-complete-accent test assertion

* refactor(project): finish review follow-ups for completion flow

- W3: reset the celebration confetti instance on dialog destroy so its
  rAF loop + window resize listener are torn down when the dialog closes
  before the animation ends (ConfettiService now returns the handle and
  fires without awaiting completion)
- S2: extract resolveBgImageToDataUrl() shared by app.component and the
  celebration dialog (was duplicated file://->data-url resolution)
- S3: split completeProject() into _getCompletionInfoOrNotify (dedupes the
  error handling), _promptResolveUnfinishedTasks and _confirmCompletion
- W2: keep prefers-reduced-motion gating app-wide (a11y) + document intent

* fix(project): close confetti teardown race on early dialog close

If the celebration dialog is dismissed while canvas-confetti is still
loading, the instance was assigned after ngOnDestroy ran, so reset() never
fired and the rAF loop + resize listener leaked. Guard with an _isDestroyed
flag and reset the instance immediately if it arrives post-destroy.

Also drop the now-dead CanvasConfetti type alias (superseded by
ConfettiInstance, zero references).

* docs(project): drop non-existent undo-snack from completion wiki

* refactor(project): extract completion task-tree and dialog helpers

* refactor(project): hide Archive menu item; Complete is the retire path

* refactor(project): drop dead archive(), reuse resolve-choice type

Multi-review follow-ups on the completion feature:
- Remove orphaned ProjectService.archive() (+ unused import, spec) — the
  menu collapsed Archive into Complete, leaving no caller. The
  archiveProject action/reducer stay for op-log decode of historical ops.
- Reuse the exported ResolveUnfinishedTasksChoice type instead of
  re-spelling the union three times in work-context-menu.
- Fix misleading moveTasksToInbox comment (setUnDone re-opens, not move).
- Note the as-shipped deviations (no extra selectors, no celebration
  effect) in the design plan so they aren't hunted for later.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-08 13:43:38 +02:00
Puneet Dixit
59c785c72b
fix(metric): use worklog totals for activity heatmap (#8013)
* fix(metric): use worklog totals for activity heatmap (#7993)

* fix(metric): refresh heatmap year after worklog load

* test(calendar): use flush() to drain dynamic-import promise

requestEvents$ awaits loadIcalModule(), which does a one-time
`await import('ical.js')`. With fakeAsync, `tick(0)` after `req.flush()`
isn't guaranteed to settle the import promise on the first invocation,
making the test order-dependent and flaky in CI.

Replace tick(0) with flush() in the requestEvents$ describe block to
drain the full microtask/macrotask queue.

---------

Co-authored-by: Deepak kudi <deepakkudi23@adsl-172-10-9-116.dsl.sndg02.sbcglobal.net>
Co-authored-by: johannesjo <johannes.millan@gmail.com>
2026-06-08 13:38:50 +02:00
felix bear
3c9dc68647
fix(trello): avoid logging raw picker results (#8130)
* fix(trello): avoid logging raw picker results #7870

* fix(trello): avoid logging issue key lists #7870

* chore(ci): rerun electron packaging for #8130

* chore(ci): rerun checks for #8130

---------

Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:46:41 +02:00
felix bear
df5f7f6d57
fix(open-project): avoid logging backlog work packages (#8126)
* fix(open-project): avoid logging backlog work packages #7870

* fix(open-project): avoid logging work package id lists #7870

* chore(ci): rerun electron packaging for #8126

* chore(ci): rerun checks for #8126

---------

Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:46:27 +02:00
felix bear
354e600ab8
fix(electron): avoid logging local file sync paths (#8127)
* fix(electron): avoid logging local file sync paths #7870

* fix(electron): return safe local sync ipc errors #7870

* chore(ci): rerun electron download checks for #8127

* chore(ci): rerun checks for #8127

---------

Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:46:13 +02:00
Johannes Millan
f7c6bd4737 test(karma): guard against leaked rxjs scheduler action crash
A time-based operator (debounceTime/delay/timer/interval) whose
subscription is torn down before its timer fires leaves a closed
AsyncAction whose pending setInterval still runs scheduler.flush().
rxjs then rethrows Error('executing a cancelled action') synchronously
inside the timer callback, surfacing in an unrelated spec's afterAll,
wedging Chrome and disconnecting the whole Karma session
(order-dependent master flake, e.g. run 27128353155 at ~test 2824).

Patch AsyncAction.prototype.execute in src/test.ts to no-op a cancelled
action instead of returning the Error flush rethrows. A cancelled action
must not run anyway, so this is semantically correct; only the diagnostic
is dropped (warn-once breadcrumb retained). Live actions delegate to the
original execute unchanged. Covers async/asap/animationFrame schedulers
(QueueAction is synchronous, cannot leak a timer).

Add test-harness-async-action-guard.spec.ts: disabling the patch makes
it throw the exact error; with it the flush is a no-op.
2026-06-08 12:38:51 +02:00
Johannes Millan
0d1869263f docs: remove outdated and implemented plan docs
Delete 29 plan/design docs whose work has shipped or been superseded
(SuperSync slices, sync-core extraction, encryption-at-rest drafts,
document-mode/Stage-A persistence, calendar/CalDAV concepts, focus-mode
time-tracking sync, etc.).

Kept the still-forward-looking docs (e.g. supersync-encryption-at-rest,
sync-core-simplification-roadmap, calendar-two-way-sync-technical-analysis).

Source comments that cited deleted docs are rewritten into self-contained
inline rationale so no "see docs/..." reference dangles.
2026-06-08 12:38:51 +02:00
felix bear
71f4ca484c
fix(plugins): return dialog result #5239 (#8106)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:14:08 +02:00
felix bear
d82754faf2
feat(supersync): configure server bind host #7301 (#8108)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:12:49 +02:00
felix bear
00cd665e1a
feat(backup): configure automatic backup file limit #6690 (#8094)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:09:49 +02:00
felix bear
049c75e7f3
refactor(scripts): share log migration helper #7917 (#8116)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:09:45 +02:00
felix bear
01183111dc
refactor(core): share markdown task parsing setup #7918 (#8115)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:09:40 +02:00
felix bear
376e068412
fix(electron): respect tray title settings #7823 (#8097)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:07:09 +02:00
felix bear
cb53bb97ff
fix(jira): skip transition for target status #5940 (#8102)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 12:07:03 +02:00
Johannes Millan
e9a7b3e7db
fix(reminder): dismiss reminder dialog when reminders vanish after sync (#8105)
* fix(reminder): dismiss reminder dialog when reminders vanish after sync

The reminder dialog's displayed list was only updated by explicit user
actions or by the worker emitting active reminders. The worker only ever
signals reminders that ARE due, never that one has disappeared, so when a
reminder was dismissed, completed or deleted on another device and synced
in, the open dialog kept showing the now-stale entry indefinitely.

Reconcile the displayed reminders against the live store: drop a reminder
once its task loses the relevant remindAt/deadlineRemindAt, is completed,
or is deleted, and close the dialog when nothing is left.

To avoid re-introducing the open-time race (worker snapshot briefly ahead
of the store) that an earlier fix relaxed, a reminder is only auto-dismissed
if it was confirmed present in the store at least once and then vanished.
The reconcile subscription is cancelled eagerly on close, mirroring the
existing onRemindersActive$ handling.

* refactor(reminder): gate dialog reconcile watcher and broaden tests

Follow-up to the reminder-dialog sync-dismiss change, addressing review
feedback:

- getByIdsLive$ (selectTasksById) emits a fresh array on every task
  mutation app-wide, so the reconcile watcher ran far more often than
  needed. Add a distinctUntilChanged that only lets through changes to the
  watched tasks' id/isDone/remindAt/deadlineRemindAt.
- Add tests for: a future-rescheduled reminder is kept (not dropped),
  all reminders vanishing at once closes exactly once, isAllDeadline
  recomputes on partial drop, redundant re-emission after a partial drop
  is a no-op, a dropped reminder does not reappear if the store re-adds it,
  and no further close after the dialog is already closed. Strengthen the
  destroy test to assert the reconcile path closed the dialog first.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-08 11:41:08 +02:00
felix bear
128fd73342
fix(jira): fill yesterday worklog time #4384 (#8101)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:41 +02:00
felix bear
db9f605ab7
fix(tasks): focus context submenus for typeahead #7987 (#8098)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:36 +02:00
felix bear
95fc91e185
fix(tasks): keep cleared Today add-task date #5339 (#8091)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:32 +02:00
felix bear
16735dbf05
fix(notes): wrap long inline markdown words #7482 (#8088)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:27 +02:00
felix bear
3221cf001c
fix(work-context): align settings dialog size #8048 (#8087)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:23 +02:00
felix bear
667e8c3aa0
fix(azure-devops): simplify host setup #7672 (#8086)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:19 +02:00
felix bear
c0146e2307
feat(help): add create task how-to #8015 (#8079)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:14 +02:00
felix bear
3dd328ba80
fix(tasks): plan started tasks for today #8031 (#8076)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:09 +02:00
felix bear
03644e8234
fix(core): refresh day stream after next-day start changes #7720 (#8075)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:05 +02:00
felix bear
9ca4d49693
fix(daily-summary): exclude future recurring tasks #5488 (#8099)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:40:00 +02:00
Johannes Millan
4bd4a4636b
fix(tasks): hide paste image hint on touch devices (#8103)
* fix(tasks): hide paste image hint on touch devices

* fix(tasks): remove paste image hint from task detail panel

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-08 11:35:10 +02:00
felix bear
b0ccaf0425
fix(tasks): support emoji project menu typeahead #8085 (#8089)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:50 +02:00
felix bear
321f4fb503
fix(boards): translate board modal labels #8070 (#8084)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:45 +02:00
felix bear
803942f3ef
fix(android): support Vanadium WebView #8005 (#8082)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:41 +02:00
felix bear
dd8eaf712e
fix(util): support technical symbol emojis #7896 (#8080)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:36 +02:00
felix bear
b38d105549
fix(projects): color visibility menu project icons #8029 (#8074)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:31 +02:00
felix bear
6a39785f6e
fix(issue): avoid logging provider config updates (#8129)
* fix(issue): avoid logging provider config updates #7870

* fix(issue): harden provider config log metadata #7870

---------

Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:27 +02:00
felix bear
2f4f9ebdb6
fix(core): avoid logging crash user data #7870 (#8128)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:23 +02:00
felix bear
18823a68f5
fix(issue): avoid logging raw http errors #7870 (#8125)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:18 +02:00
felix bear
09469e64e5
fix(jira): avoid logging raw issue payloads #7870 (#8124)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:14 +02:00
felix bear
06a17b4b9b
fix(tasks): avoid logging issue search task result #7870 (#8123)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:09 +02:00
felix bear
eb5f353021
fix(tag): avoid logging tag state on reorder #7870 (#8122)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:27:04 +02:00
felix bear
5808b779ac
fix(plugins): avoid logging extracted zip data #7870 (#8121)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:26:59 +02:00
felix bear
8adaa345ca
fix(tasks): avoid logging short syntax result #7870 (#8119)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:26:54 +02:00
felix bear
9b2104a391
fix(nav): avoid logging dragged task objects #7870 (#8118)
Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-08 11:26:50 +02:00
Johannes Millan
c13e8ee25b test(e2e): fix worklog finish-day navigation race #8033
The worklog 'show worklog after completing tasks' test waited on
waitForSelector('task-list') after Save & go home, but daily-summary
renders its own task-list (planner-day), so that resolved immediately
without waiting for finishDay's deferred router.navigate(['/active/tasks']).
The test then went to /history and the pending redirect clobbered it,
so .week-row never rendered (timeout, screenshot showed Today).

Apply the proven sibling pattern (commit 5c73ec6): gate Save on
waitForURL(TODAY/tasks) before navigating to History, assert the
History URL, and expand via .day-toggle with an aria-expanded check.
2026-06-06 20:09:59 +02:00
Johannes Millan
b59b266719 test(e2e): unbreak boards done-toggle and time-tracking selector
markTaskAsDone() threw on locators without data-task-id, breaking the
Boards #7498 specs that pass <planner-task>. Branch on the id instead:
<task> rows keep the dup-row done-state wait; wrapper rows (which can
relocate panels on done) just settle and let the caller assert.

Scope the time-tracking spec's main play button to play-button .play-btn
so it no longer collides with a task's .start-task-btn (same play_arrow
icon) once the task is in the TODAY view.
2026-06-06 19:00:50 +02:00
Johannes Millan
5c73ec6220 test(e2e): stabilize finish day history flow 2026-06-06 19:00:50 +02:00
摇摆熊
645797f0bd
fix(reminder): require action for reminder popup #8051 (#8057)
* fix(reminder): require action for reminder popup #8051

* test(reminder): update deadline reminder e2e expectation

---------

Co-authored-by: cocojojo5213 <cocojojo5213@users.noreply.github.com>
2026-06-06 17:34:06 +02:00