Commit graph

466 commits

Author SHA1 Message Date
johannesjo
3bb07fc6d0 fix(sync): address package review feedback 2026-05-14 12:11:48 +02:00
johannesjo
6797e9eed2 refactor(sync): tighten extracted package surfaces
Combined polish from the post-extraction review:

- sync-core: strip NgRx-shaped types from EntityConfig/EntityRegistry;
  expose host extensions via generic param. Move StateSelector,
  PropsStateSelector, SelectByIdFactory, SelectById, EntityUpdateLike,
  EntityAdapterLike to a new app-side entity-registry-host.types.ts.
- sync-core: mark OpType.SyncImport/BackupImport/Repair as @deprecated;
  hosts should use createFullStateOpTypeHelpers().
- sync-providers: resolve provider.types.ts vs provider-types.ts
  duplication; inline implementation into the dashed canonical name.
- sync-providers: drop unused root barrel and "." export; consumers
  already use focused subpath barrels (/dropbox, /webdav, etc.).
- sync-providers: replace wildcard "@sp/sync-providers/*" tsconfig path
  alias with 11 explicit subpath entries matching package.json exports;
  deep-internal imports now fail at typecheck.
- sync-providers: move @sp/sync-core from dependencies to
  peerDependencies (kept in devDependencies for tests).
- both packages: add composite: true to enable project references;
  introduce tsconfig.build.json overlay so tsup DTS bundler still works.
- gitignore: ignore **/*.tsbuildinfo composite outputs.
2026-05-14 00:13:25 +02:00
Johannes Millan
001e9847b4 build(electron): scope tsc path aliases to package dist types
tsc -p electron/tsconfig.electron.json was resolving @sp/* aliases to
packages/*/src/*.ts, then transitively compiling those sources and
emitting .js next to each .ts (no outDir is set). Every electron-bearing
task (electron:build, npm start, build, dist) re-littered
packages/{sync-core,sync-providers,shared-schema}/src with stale .js
shadows; gitignore hid them from git but they still shadowed the .ts
sources for Vitest, silently breaking module mocks.

Point the aliases at dist/index.d.ts (and dist/* for subpaths) so tsc
consumes declarations only and never touches package source. Runtime
resolution is unaffected (Node uses the package.json exports).
2026-05-13 22:01:20 +02:00
Johannes Millan
a817a300c5 fix(build): add @sp workspace path mappings to electron tsconfig
The electron tsc transitively type-checks src/app/op-log/core/types/sync.types.ts
(via electronAPI.d.ts -> model-config -> sync.types). Since 00098f52fb introduced
subpath imports like '@sp/sync-providers/dropbox', and electron's tsconfig uses
moduleResolution 'node' without paths, resolution fell back to node_modules where
classic node resolution can't honor the package's 'exports' field (.d.mts types).

Mirror the @sp/* path mappings from tsconfig.base.json so subpath imports resolve
to the workspace source under packages/sync-providers/src/, restoring the CI build.
2026-05-13 15:48:27 +02:00
Het Savani
a3560a5615
feat(settings): add image picker for background image selection (#7564)
* feat(settings): add image picker for background image selection

* fix(settings): use electron file paths for background image selection

* fix(settings): validate and normalize local background image paths

* fix(theme): validate local background image types before reading files
2026-05-13 12:58:07 +02:00
Johannes Millan
e7454ea6b9 fix(window): preserve maximized state for shortcut hide 2026-05-11 14:31:19 +02:00
David Vornholt
a7845acd81
fix(electron): retry Wayland idle helper startup (#7527)
* fix(electron): retry wayland idle helper startup

* build(electron): enforce wayland helper in CI

* fix(electron): avoid blocking wayland idle promotion

* test(e2e): make reminder option selector exact

* test(e2e): resolve reminder option selector conflict

* fix(electron): gate wayland helper redetection

* fix(electron): avoid stale Wayland helper packaging

---------

Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
2026-05-10 22:35:33 +02:00
johannesjo
d5a7b5f936 fix(screenshots): force regular activation policy + dock on macOS
When the screenshot fixture launches Electron via Playwright's
`_electron.launch`, the binary runs as a child of the Node test runner
instead of being launched as an .app bundle. macOS doesn't promote
child-spawned Electron processes to full GUI apps — the result is a
window without the hiddenInset traffic-lights, even though the same
SP build launched via `npm start` shows them.

Force `app.setActivationPolicy('regular')` and `app.dock.show()` from
start-app.ts when `SP_SCREENSHOT_MODE=1` so the screenshot pipeline
gets full app status; normal users are unaffected.
2026-05-09 11:39:36 +02:00
johannesjo
a6b8159d5b feat(screenshots): liquid-glass-ready electron pipeline + diagnostics
Round of robustness fixes for the store-screenshot pipeline so the
Liquid Glass captures actually ship MAS-compliant PNGs:

Capture & dimensions:
- Capture by CGWindowID on macOS (`screencapture -l <id>` resolved via
  desktopCapturer.getSources matched on window title) instead of by
  screen rect. Survives small displays clipping the window and grabs
  the full hiddenInset titlebar including traffic-lights regardless of
  off-screen position.
- `enableLargerThanScreen: true` on the BrowserWindow when launched in
  screenshot mode (gated on a new SP_SCREENSHOT_MODE=1 env var). Keeps
  the configured 1280×800 outer from being clamped down to fit the area
  below the menu bar / dock — without this, captures came out 20pt
  short and Mac App Store rejected the dimensions.
- Drop `--no-sandbox` / `--disable-dev-shm-usage` from the Electron
  launch on macOS. They're Linux/CI helpers; on Mac `--no-sandbox`
  empirically suppresses the hiddenInset traffic-lights even though the
  same SP build launched via `npm start` shows them.

Configurability:
- `SCREENSHOT_CUSTOM_THEME` env var feeds the fixture's `customTheme`
  option, so a one-off pipeline run under e.g. liquid-glass / dracula
  doesn't require editing spec files.
- New `scrollScheduleUp` helper called from every schedule capture
  (desktop slots 03 + 06, mobile 05, tablet 03) — nudges the schedule
  scroll-wrapper up ~80px so the captured frame shows context before
  work-start instead of being flush against it.
- New slot-02 light variants for desktop (eisenhower) and mobile
  (planner-expanded) so both light and dark land at slot 02.

Diagnostics:
- Surface execFile stderr/stdout in the capture-failed Error so
  permission failures name the actual cause (`could not create image
  from rect`, `not authorized`, etc.) instead of just "Command failed".
- Loud banner on first OS-capture failure plus an end-of-run summary
  (via the existing globalTeardown) re-surfacing the warning so it
  isn't lost in long log scrollback. Marker file in MASTER_DIR
  bridges fixture → globalTeardown.
- Per-capture `[screenshot] <name> → <bin> <args>` log line, plus an
  `setBounds requested=… achieved outer=…; content=…` line right after
  the resize, so a "missing chrome" run is debuggable from the test
  output alone.
- README: macOS Screen Recording permission requirement documented.
2026-05-09 11:17:13 +02:00
johannesjo
21472356c7 fix(theme): polish liquid-glass — readability, parity, wallpaper, badges
Round of refinements on top of the Liquid Glass theme to take it from
"works" to "ships":

- Default to Liquid Glass on Apple Silicon Macs. Web / Intel / Linux /
  Windows still default to the system theme. Adds an `isAppleSilicon()`
  preload bridge + `IS_APPLE_SILICON` constant, and a CustomTheme
  default selector that picks Liquid Glass when both Electron and arm64
  are detected.
- macOS title-bar inset for the floating side-nav (titleBarStyle
  switched to 'hiddenInset' so AppKit positions traffic-lights at the
  standard inset other native apps use).
- Wallpaper handling: blur `.bg-image` directly so the whole page sees
  one out-of-focus image (no seam at the side-nav edge), drop
  `.main-content` / side-nav backdrop-filters under `body.hasBgImage`
  so the source-blur isn't re-blurred at different strengths, and hide
  the white/black `.bg-overlay` scrim that was washing out the glass.
- Surface opacity: bump every Tier-A fill (tasks, sub-tasks,
  schedule-events, planner-tasks, cards, banner, right-panel, notes,
  task-detail, attachments, sidenav) to 0.78–0.95 alpha so text reads
  cleanly whether the background is the soft primary-radial gradient
  or a user-selected photo. Light + dark aligned to the same alpha.
- Dark-mode further: tasks / sub-tasks / schedule-events / task-detail
  go fully opaque (deep RGB stepped ~10 units toward black) so content
  surfaces stay legible against the wallpaper while chrome / containers
  keep the soft glass material.
- Schedule weekday / week-column headers: bind to a solid --surface-2
  fill instead of the translucent --bg-lighter / transparent --bg the
  defaults used.
- Task time-badge / repeat-date-badge: solid surface (white in light,
  surface-4 in dark, slightly brighter than the task fill so the badge
  reads as a raised label rather than a recessed hole). Defaults bound
  it to translucent tokens, and the icon underneath bled through.
- Two stylelint suppressions for the new layered-token sections in
  _css-variables.scss (Cat B follows Layer 1 primitives on the same
  body / body.isDarkTheme selector — intentional duplicate).
2026-05-09 11:16:25 +02:00
Johannes Millan
4d8605baa3 fix(security): harden XSS sinks and add Origin check (#7413)
Address Snyk findings raised in discussion #7413:

- error-handler: inject error title, additionalLog, stackTrace, and
  meta into the global error alert via textContent/setAttribute instead
  of innerHTML interpolation. Also fixes a latent <h2> close-tag typo.
- break-reminder-overlay: use textContent (not innerHTML) for the
  reminder message; switch to decodeURIComponent to match a producer-
  side encodeURIComponent change (also fixes a latent parse bug for
  messages containing & or =).
- local-rest-api: reject any request that arrives with a web Origin
  header. Closes the simple-POST CSRF gap (text/plain bodies are not
  preflighted by CORS) on top of the existing Host allowlist.

The Snyk findings on Google OAuth CLIENT_SECRET (Desktop client per
RFC 8252) and proxy-agent rejectUnauthorized (opt-in for self-hosted
WebDAV with self-signed certs) are documented false positives and
intentional, respectively; both already carry explanatory comments.
2026-05-06 21:37:06 +02:00
Johannes Millan
b9516b5ac7
Improve OAuth error handling and reporting (#7445)
* fix(plugin-oauth): surface real error and propagate state on local errors

Clicking "Connect Google Account" could show the generic
"Authentication failed!" snack with no detail. Two issues caused this:

1. The catch in connectOAuth swallowed the actual error. Log it and
   include the message in the snack so failures are diagnosable.
2. When the Electron main process emitted a local OAuth error
   (invalid_auth_url, failed_to_open_browser), the IPC payload had no
   state, and handleRedirectError silently dropped any callback whose
   state did not match. Echo the state from the auth URL on the main
   side, and treat missing-state errors as trusted local failures on
   the renderer side (they are not CSRF-relevant). This rejects the
   pending flow immediately instead of waiting for the 5-minute timeout.

* fix(plugin-oauth): apply review feedback on connect-OAuth UX

- Split the try block in connectOAuth so a failure of _loadDynamicOptions
  no longer surfaces as an "Authentication failed" snack on top of the
  success snack. The OAuth connection itself succeeded; loadOptions has
  its own per-field error reporting.
- Sanitize the surfaced error: collapse whitespace and cap to 200 chars
  so a long HttpErrorResponse message doesn't blow up the snack.
- Log the message field instead of the raw Error, per CLAUDE.md
  anti-pattern #11 (log history is exportable).
- Use undefined instead of null for the state echo in the main process
  to match the renderer/preload signatures.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-01 18:45:11 +02:00
Nicholas Dias
7a51d4b06e
feat: allow dev override for local REST API (#7440) 2026-05-01 16:54:26 +02:00
Florian Bachmann
6c1a0b54f9
Caldav subtask import (#7409)
* fix(electron): disable webSecurity in dev mode to suppress CORS preflights

* feat(CalDAV): Option to import sub-tasks along with parent

* test(CalDAV): Adds specs for option to import subtasks along with parent

* doc(CalDAV): Adds notion on automatic sub-task import

* feat(CalDAV): Handles grandchildren and ignores subtasks of archived tasks.

* fix(setup): removes policy violating code

* fix(caldav):_resolve N+1 issue

* test(caldav):_adds more tests

* fix(electron):_fix task-widget mock in electron test
2026-05-01 13:07:03 +02:00
Johannes Millan
258a6c8949 feat(task-widget): make settings per-instance and fix Mac drag/resize
OS behavior (transparency, native drag/resize) differs enough between
platforms that syncing one shared value across devices makes no sense.
Move taskWidget settings out of the synced GlobalConfigState into a
localStorage-backed TaskWidgetSettingsService and a dedicated
UPDATE_TASK_WIDGET_SETTINGS IPC channel.

On macOS, transparent + frameless BrowserWindows do not support native
edge resize or window drag (Electron docs: "Transparent windows are not
resizable"). Use transparent: false on Mac and apply user-set opacity
via BrowserWindow.setOpacity() so native drag/resize keep working.

GlobalConfigFormSectionKey is split from GlobalConfigSectionKey so that
'taskWidget' cannot leak into updateGlobalConfigSection action payloads
(which would create phantom GLOBAL_CONFIG_UPDATE_SECTION ops).

No data migration: users previously configuring the widget will see
defaults and reconfigure once.
2026-04-29 21:16:12 +02:00
Johannes Millan
077bed154b fix(electron): use native images for Linux tray icons
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-27 15:04:06 +02:00
Johannes Millan
b642415702 fix(sync): improve Dropbox auth dialog UX on sandboxed Linux (#7139)
The "Get Authorization Code" button silently failed on Flatpak because
shell.openExternal rejects without renderer feedback when the
org.freedesktop.portal.Desktop talk-name isn't granted. After the user
gave up and reopened the dialog, the second attempt failed again with
`invalid_grant: invalid code verifier` because each call to
Dropbox.getAuthHelper() generated a fresh PKCE verifier that no longer
matched the originally-shown URL's challenge.

Three changes:

- Cache the in-flight PKCE Promise on the Dropbox provider so concurrent
  callers and consecutive dialog opens share one verifier+URL pair.
  Cleared on successful exchange, on clearAuthCredentials(), and on a
  rejected generation (so a one-time crypto failure doesn't poison the
  session). Five regression tests cover reuse, success-clear, explicit
  clear, concurrent calls, and rejection-recovery.

- Render the auth URL as user-selectable text under a <details>
  disclosure. Escape hatch when both shell.openExternal and the
  clipboard portal are denied — the user can triple-click to select and
  Ctrl+C the URL into a manually-opened browser. Adds
  D_AUTH_CODE.MANUAL_URL_HINT translation key.

- Pipe shell.openExternal rejections through
  errorHandlerWithFrontendInform so the existing IPC.ERROR snack
  channel surfaces a "Could not open the link in your browser" message
  instead of swallowing the failure to electron-log. Wrapped in a
  try/catch since errorHandlerWithFrontendInform throws synchronously
  if the renderer isn't ready.

The Flathub manifest also needs --talk-name=org.freedesktop.portal.Desktop
and --socket=wayland to fully fix the user-reported issue, but that
change lives in the flathub repo.
2026-04-25 22:36:14 +02:00
David Vornholt
0988c2d8c6
Add ext-idle-notify backend for Wayland idle detection (#7337)
* fix(electron): support Wayland idle detection via ext-idle-notify

* fix(electron): address wayland idle helper review

---------

Co-authored-by: Johannes Millan <johannesjo@users.noreply.github.com>
2026-04-24 16:50:24 +02:00
Johannes Millan
99b7dee74a fix(backup): move shared timestamp util into electron/ for snap packaging
electron/backup.ts imported getBackupTimestamp from src/app/util/, which
tsc compiled alongside the source. electron-builder only packages
electron/** and the Angular renderer bundle, so the compiled util was
missing from app.asar and the main process crashed on launch with
"Cannot find module '../src/app/util/get-backup-timestamp'".

Moved the util to electron/shared-with-frontend/ (existing convention
for code shared between main and renderer), updated all importers.

Fixes #7270
Refs #7315, #7323, #7265, #7320
2026-04-22 16:14:56 +02:00
Johannes Millan
297aeb30ab fix(electron): harden Snap+Wayland argv wrapper after multi-review
Address findings from code-reviewer + debugger-assistant +
refactor-specialist multi-review on 8e1c47ebc2:

- afterPack: read wrapper source before rename; on writeFile failure,
  roll the rename back so a broken intermediate state can't ship a
  package with no launcher. Strengthen idempotency check to require a
  shebang at binPath (both files alone no longer counts as "installed").
- wrapper: gate the X11 injection on \$SNAP_NAME = "superproductivity",
  not just \$SNAP set. An xdg-open from a sibling snap (e.g. Firefox)
  leaks \$SNAP into the child env, which previously would have silently
  forced XWayland on .deb/.rpm users. Derive BIN_DIR from \$SNAP
  directly when we are our snap, avoiding fragile \$0 resolution
  through snapd's wrapper chain. Stop argv scanning at -- so positional
  args that resemble --ozone-platform aren't misread as a user override.
- app-control: point app.relaunch() execPath at the sibling shell
  wrapper instead of the default process.execPath (which is the renamed
  ELF and would bypass the flag injection on a relaunched Snap+Wayland
  instance).
- Move build/snap-wrapper.sh -> build/linux/snap-wrapper.sh to match
  the existing build/linux/ layout convention.
- Update research doc §18 to reflect the tightened predicate and the
  relaunch fix.
2026-04-21 15:03:42 +02:00
Johannes Millan
0f99f38ca9 fix(window): persist maximized state across tray-quit (#7276)
electron-window-state reads isMaximized() on an already-hidden window in
its `closed` handler, which returns false (electron#27838). Patch the
persisted state file in will-quit using our own wasMaximizedBeforeHide
flag, which is kept accurate by the maximize/unmaximize event listeners.
2026-04-21 15:03:41 +02:00
aakhter
453d980a41
fix(electron): harden simple store writes (#7297)
* fix: harden electron simple store writes

* fix(electron): harden simple store writes

---------

Co-authored-by: Aamer Akhter <aamer_akhter@users.noreply.bitbucket.org>
2026-04-21 15:03:30 +02:00
novikov1337danil
1abb15b804
refactor(backup): unify backup filename format (#7141)
* refactor(backup): implement consistent timestamp for backup filenames

* refactor(i18n): rename PRIVACY_EXPORT key to PRIVACY_EXPORT_DESCRIPTION

* refactor(i18n): add PRIVACY_EXPORT key for data export in multiple languages

* refactor(backup): standardize backup filename prefix

* refactor(backup): update backup filename format to use underscore separator

* refactor(i18n): update translations

* refactor(backup): move getBackupTimestamp utility to a shared location for consistency

* test(backup): add unit tests for getBackupTimestamp utility

* fix(e2e): move MIGRATION_BACKUP_PREFIX to migration.const

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor(tests): move jasmine.clock().install() to beforeEach for consistency

* refactor(backup): consolidate backup filename constants and update imports

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-21 14:09:09 +02:00
Johannes Millan
cbf3eb15d4 fix(electron): add GPU startup guard for confined Linux packages
Defense-in-depth against GPU init failures on Snap/Flatpak Linux where
the main process stays alive but the GPU process crashes at init and
the window never renders. Field data in #7270 (two post-v18.2.4
reports) shows this happens on Ubuntu 24.04+/25.10 regardless of GPU
vendor — the driver is core22 Mesa/libgbm drifted from the host Mesa.
See §12–§17 in docs/research/snap-wayland-gpu-fix-research.md.

Mechanism (electron/gpu-startup-guard.ts):

- Content-based crash marker in userData with {ts, electronVersion}.
  Written before app.whenReady() on confined Linux; cleared via
  IPC.APP_READY after Angular boot — not ready-to-show, which fires
  on blank/broken renderers too.
- Previous-crash detection: marker present AND recent (<5 min) AND
  matching Electron version. Staleness bound + version gating drop
  systemd-SIGKILL-mid-boot and post-upgrade-residue false-negatives.
- Env overrides SP_DISABLE_GPU=1 / SP_ENABLE_GPU=1 work on all
  platforms; auto-detection is Linux+Snap/Flatpak-only.
- Non-ENOENT fs errors logged at warn — a swallowed write-fail
  previously meant the guard could re-enter the loop with no
  diagnostic trail; a swallowed unlink-fail meant a successful boot
  could get permanently stuck in crash-recovery.

Fallback flag bundle (start-app.ts):
  --disable-gpu
  --disable-software-rasterizer
  --ozone-platform=x11

The pair matches Chromium's GPU integration tests' "no GPU process"
invariant; DisplayCompositor handles 2D in the browser process
without spawning a GPU child. app.disableHardwareAcceleration()
alone does NOT — verified against electron/electron#17180/#20702.
The extra --ozone-platform=x11 closes the Chromium 140+
browser-side Wayland/libgbm-dlopen gap on Flatpak (redundant with
the existing Snap X11 widening branch; last flag wins).

Novelty: R3 survey of VS Code, Slack, Insomnia, LosslessCut,
Obsidian flatpak, Firefox snap, and Canonical's gpu-2404-wrapper
found no peer Electron-snap implementing an equivalent reactive
crash-detection + auto-fallback. Prevailing patterns are manual
--ozone-platform=x11, proactive env-sniff, or do-nothing.

Review: §14–§15 multi-agent verification on the original PR #7273;
re-reviewed via 7-agent multi-review (6 Claude focus-agents + Codex
CLI) + 5 research agents here (§17). Live-tested with
SP_DISABLE_GPU=1 on a KDE/X11 dev host — window rendered normally
via DisplayCompositor.

Refs: #7270
2026-04-20 18:35:31 +02:00
Johannes Millan
2cefde61bf fix(electron): keep window reachable when show/hide shortcut has no tray
The globalShowHide shortcut called mainWin.hide() unconditionally. On
Windows/Linux hide() removes the taskbar entry, so with no tray icon
the window became unreachable.

- macOS: use hide() (dock icon always remains).
- Windows/Linux: hide to tray only when minimize-to-tray is on AND the
  tray was created; otherwise minimize() to keep a taskbar handle.
- Log whether Windows tray creation used the GUID path, to help diagnose
  cases where the icon lands in the hidden overflow area.

Refs #7282
2026-04-20 15:48:40 +02:00
Johannes Millan
01e30b9c7e
Feat/to me it looks like there are lots of 60dd04 (#7280)
* fix(issue): prevent crash from orphan issueProviderId (#7135)

The Jira image-headers effect in task-detail-panel subscribed to
selectIssueProviderById without an error handler, so a task with an
issueProviderId pointing at a deleted provider (e.g. after sync
convergence where taskIdsToUnlink didn't cover all local tasks)
propagated the selector throw to Zone.js as a crash dialog. Wrap the
inner selector observable in catchError that logs and falls back to
of(null); the downstream jiraCfg?.isEnabled guard handles the fallback.

Also drop IssueLog.log(issueProviderKey, issueProvider) from the
throwing variant of the selector: providers may carry credentials
(host, token, apiKey) and IssueLog history is exportable.

* fix(focus-mode): sync tray countdown with in-app timer during breaks

Tray title was rebuilt from a cached currentFocusSessionTime that only
refreshed when CURRENT_TASK_UPDATED fired. addTimeSpent is gated on an
active current task, so during focus-mode breaks or task-less focus
sessions the cache froze while the in-app timer kept ticking.

Add the tick action to taskChangeElectron$ so the cache refreshes every
second whenever the focus timer is running.

Fixes #7278

* fix(ci): restore GitHub Actions SHA pins undone by 0e9218bd68

Commit 0e9218bd68 silently reverted PR #7212 (github-actions-minor group
bump) along with its stated sync/client-id work. This restores the 15
workflow files to their pre-revert state.

Actions restored to newer pinned SHAs:
- actions/upload-artifact v7.0.0 -> v7.0.1
- step-security/harden-runner v2.16.1 -> v2.17.0
- softprops/action-gh-release v2.6.1 -> v3.0.0
- signpath/github-action-submit-signing-request v2.0 -> v2.1
- anthropics/claude-code-action v1.0.89 -> v1.0.93
- docker/build-push-action v7.0.0 -> v7.1.0
- easingthemes/ssh-deploy v5.1.1 -> v6.0.3

* fix: restore i18n, UI, and docs work undone by 0e9218bd68

Commit 0e9218bd68 silently reverted the following work alongside its stated
sync/client-id changes. Files where later master commits (fec7b25f23, etc.)
already re-applied the reverted work are intentionally left untouched.

Restored:
- #7232 docs/long-term-plans/location-based-reminders.md (513 lines)
- #7199 Romanian i18n phase 3 (ro.json + ro-md.json, ~1168 lines)
- #7049 Polish translation improvements
- #7143 planner component styling (4 scss files)
- #7211 add-task-bar preserve time estimate when typing title
- #7208 task.reducer roll-up estimates for added subtasks
- #6767 focus-mode pomodoro reset button
- #7205 plugin-dev github-issue-provider TOKEN description
- #7231 mobile-bottom-nav FAB fix
- a4fe03272 iOS keyboard accessory bar (global-theme + dialog-fullscreen-markdown)
- 309670db3 ShortSyntaxEffects undefined guard
- 667a7986f Dropbox PKCE auth comment/behavior

* fix(electron): restore electron + e2e work undone by 0e9218bd68

Commit 0e9218bd68 silently reverted the following electron/e2e work.
Files already re-fixed by later master commits are left as-is:
- e2e/tests/sync/supersync-archive-conflict.spec.ts (de33234976 + 191d129ff3)

Restored:
- e8a3e156eb fix(electron): Linux autostart IDB backing-store recovery
  (re-adds electron/clear-stale-idb-locks.ts + start-app.ts wiring)
- 5ce78a5b63 fix(electron): macOS Cmd+Q / Dock > Quit hang
  (setIsQuiting + before-quit delegate to close-handler)
- 46e0fa2d01 fix(sync): FILE_SYNC_LIST_FILES IPC contract
  (electronAPI.d.ts + local-file-sync + preload + ipc-events)
- ea1ef16307 fix(android): session-only SAF permissions on OEM devices
- 8865dc0a50 test(e2e): supersync parallel-worker stampede guard
  (SUPERSYNC_SERVER_HEALTHY env-var fallback + goto retry loop)
- af7c7687e2 test(e2e): block WS-triggered downloads in non-WS specs
- 265b44db5d test(e2e): premature waitForURL on daily-summary
  (this is literally the fix the bad commit's message claimed to add)

Conflict resolutions:
- e2e/utils/supersync-helpers.ts: kept the refined getDoneTaskElement
  checks from d64014d086 (later than c558bcab5e) while restoring the
  goto retry loop from 8865dc0a50.
- electron/start-app.ts: unioned imports (setIsQuiting +
  clearStaleLevelDbLocks from theirs, fs from ours).

* fix(sync): restore sync-core work undone by 0e9218bd68

Commit 0e9218bd68 silently reverted parts of several sync fixes. Most
sync-core reverts have already been re-addressed differently on master
by later commits (1f5184f6e7, 05cd875dd6, 09f5ced2c9, 7df43358ab,
d9158d6adb, 32dbc95ed9, 8c3b08e016, f89fe1ebc3) — those files are
intentionally left untouched to avoid reverting master's newer work.

This PR restores only the pieces that are genuinely still missing:

- e8a3e156eb fix(electron): IDB backing-store autoreload (in-app piece)
  operation-log-hydrator.service.ts + .spec.ts (the electron/clear-
  stale-idb-locks.ts piece was restored in the prior commit)

Plus three low-risk documentation/cleanup restorations:
- operation-sync.util.ts — add "Nextcloud" to isFileBasedProvider JSDoc
- dropbox.ts — restore improved _getRedirectUri JSDoc (667a7986fb)
- dialog-get-and-enter-auth-code.component.ts — restore isNativePlatform
  comment explaining why manual code entry flow is used (667a7986fb)
- file-adapter.interface.ts — remove stray "// NEW" comment (46e0fa2d01)

Intentionally NOT restored (master's newer work covers or supersedes):
- sync-trigger.service.ts / sync.effects.ts (05cd875dd6)
- sync-wrapper.service.ts (1f5184f6e7)
- sync-errors.ts (1f5184f6e7 re-added LegacySyncFormatDetectedError)
- file-based-sync-adapter.service.ts + spec (1f5184f6e7 + d9158d6adb)
- file-based-sync.types.ts (1f5184f6e7)
- operation-log.const.ts (32dbc95ed9 bumped IDB_OPEN_RETRIES to 5)
- dialog-sync-initial-cfg.component.ts (f89fe1ebc3)
2026-04-20 12:04:38 +02:00
Symon Baikov
3c75b527cd
fix(electron): recreate tray indicator before minimize to tray (#7072)
* fix(electron): recreate tray indicator before minimize to tray

* fix(electron): handle missing tray indicator gracefully
2026-04-18 22:05:16 +02:00
Johannes Millan
ac7cf7b853
Claude/snap wayland gpu fix co dc5 (#7266)
* docs(research): add Snap + Wayland GPU init failure research

Synthesizes root cause (Mesa ABI drift in gnome-42-2204), scope,
peer-app consensus, and ranked options. Recommends widening the
existing Snap-gated --ozone-platform=x11 guard in start-app.ts to
cover Snap + Wayland sessions, with core24 + gpu-2404 migration
as the long-term fix.

https://claude.ai/code/session_01Hu6EP7Xux9JRvGGGKpBfwm

* docs(research): correct electron timing and soften unverified peer claims

* fix(electron): force X11 on Snap Wayland to avoid Mesa ABI drift

The existing Snap-only X11 fallback triggered only when $SNAP/gnome-platform
was empty. In practice gnome-platform is populated but its Mesa can drift
out of ABI sync with Electron's bundled libgbm, producing
"Failed to get system egl display" and a GPU process respawn loop on
Wayland sessions. Widen the guard to also fire when the session is
Wayland (XDG_SESSION_TYPE=wayland or WAYLAND_DISPLAY set). X11/GLX
avoids the failing Wayland EGL init path entirely while preserving
hardware acceleration.

Refs: electron-builder#9452, super-productivity#5672,
forum.snapcraft.io #40975, #49173

https://claude.ai/code/session_01JNxazJmDhpMp9UYV6SqnBG

* refactor(electron): address review feedback on Snap X11 force

- Handle space-separated `--ozone-platform wayland` override in addition
  to the `=` form; tighten the argv scan to reject accidental substring
  matches (e.g., arg values that happen to contain `--ozone-platform=`).
- Enrich the log line with raw XDG_SESSION_TYPE and WAYLAND_DISPLAY
  values to make user-reported triage faster.
- Tighten comment: correct "X11/GLX" to "X11 ozone backend" (modern
  Chromium uses EGL on X11), clarify the root cause as Mesa version
  drift rather than bundled-library ABI drift, broaden the loss list
  beyond fractional scaling, and move issue references to the commit
  body per CLAUDE.md guidance.

Follow-ups flagged but not addressed here: possible interaction with
`allowNativeWayland: true` in electron-builder.yaml, GPU cache keyed
by effective ozone platform, and the longer-term core24 migration
already in docs/long-term-plans/electron-upgrade-to-v40.md.

Refs: electron-builder#9452, super-productivity#5672,
forum.snapcraft.io #40975, #49173

https://claude.ai/code/session_01JNxazJmDhpMp9UYV6SqnBG

* docs(research): verify peer-app claims, correct SP electron timing

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-18 20:57:00 +02:00
Leandro Marques
179700ccda
chore: update electron to v41.x (#7097)
* chore: update electron to v41.1.1

* feat(start-app): clear GPU cache on Electron version change for Linux

* fix(window-decorators): disable custom window title bar for GNOME

This fixes some issues when moving and resizing the window

* refactor: Migrate url.format() (DEP0116) to file:// path approach

* refactor(start-app): remove deprecated protocol.registerFileProtocol in start-app.ts

* feat(electron-builder): update gnome content snap to gnome-42-2204 for improved compatibility and update flatpak permissions

* fix(window-decorators): improve handling of custom window title bar for GNOME

* feat(start-app): implement fallback to X11 in Snap if gnome-42-2204 runtime is unavailable

* chore: update electron to v41.1.1

* chore(package-lock): remove unused dependencies from package-lock.json

* fix(electron): move snap ozone-platform switch before app ready event

app.commandLine.appendSwitch() must be called before Chromium
initializes — after the ready event fires the GPU backend is already
running and the switch is a no-op. Move the Snap X11 fallback (defense-
in-depth for missing gnome-42-2204 runtime) to run synchronously at
startup, alongside the existing gtk-version and speech-dispatcher
switches.

* fix(electron): align IS_GNOME_DESKTOP detection with preload.ts logic

The original implementation only matched Ubuntu's GNOME session
(XDG_CURRENT_DESKTOP contains both 'gnome' AND 'ubuntu'), missing plain
GNOME on Fedora, Arch, etc. The preload.ts isGnomeDesktop() already
used the correct approach: check four environment variables with OR
logic. Align common.const.ts to match, preventing a split-brain where
the main process and renderer disagree on whether the user is on GNOME
— which would produce no title bar at all on non-Ubuntu GNOME desktops.

* fix(electron): restore v41 bump lost in merge and gate title-bar toggle

- Re-apply electron 41.2.0 + minimatch 10.2.5 override (master's 0e9218bd
  reverted the dependabot bump back to 37.10.3 while this branch's
  merge-base still contained 41.2.0, so the pre-merge diff was empty).
- Regenerate root package-lock.json accordingly.
- Drop unrelated esbuild additions from plugin-dev sub-lockfiles.
- misc-settings-form: gate isUseCustomWindowTitleBar on IS_ELECTRON &&
  !IS_GNOME_DESKTOP so the toggle does not appear in the web/PWA build.

---------

Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
2026-04-17 18:12:43 +02:00
Johannes Millan
8c3b08e016 fix(sync): add copy-URL fallback for Dropbox auth dialog (#7139)
The "Get Authorization Code" button silently fails on some Linux
packagings (Flatpak, AUR) because shell.openExternal() can reject
without visible feedback — the wasOpened boolean check in
openUrlInBrowser has been dead code since the call was migrated from
shell.openPath to shell.openExternal (which returns Promise<void>).

- Add a "Copy URL" button to the auth dialog as a reliable fallback
  users can paste into a manually-opened browser.
- Replace the dead wasOpened check with a .catch() that logs the
  rejection reason, so future portal failures surface in electron logs.
- Add regression tests documenting the PKCE verifier/challenge
  invariants that make stale auth codes fail with "invalid code
  verifier" when the dialog is reopened.
2026-04-16 19:47:52 +02:00
Johannes Millan
8d3b31f9d3 fix(electron): restore macOS lock screen by correcting osascript quoting
The osascript -e argument used nested unescaped double quotes, causing sh
to split it into multiple tokens so System Events never saw a valid
AppleScript. This has been broken since a 2022 prettier run stripped the
backslash escapes. Use a template literal so the inner quotes survive
formatting, and drop the CGSession path comment since it was removed in
Big Sur and only kept for pre-Big Sur fallback.

Closes #7217
2026-04-16 19:47:52 +02:00
Johannes Millan
7076175817 fix(electron): restore main window correctly when minimize-to-tray and task widget are both enabled 2026-04-16 17:41:39 +02:00
Johannes Millan
cf2a572c9d feat(task-widget): add max size constraints (700x120) to overlay window 2026-04-16 17:41:39 +02:00
Johannes Millan
0e9218bd68 fix(sync): handle data validation errors and improve client ID management
- 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)
2026-04-16 17:41:39 +02:00
Johannes Millan
e8a3e156eb fix(electron): recover from IndexedDB backing-store errors on Linux autostart
Three-layer defense for issue #7191 (Flatpak/Snap/autostart startup race):

1. Increase IDB_OPEN_RETRIES 3→4 (total retry window 3.5s→7.5s) to give
   OS/Flatpak storage more time to initialize before giving up.

2. Silent auto-reload on first backing-store error: instead of a blocking
   alert that requires user interaction before the reload (bad for autostart
   where nobody is watching), reload immediately and only show the manual
   recovery dialog if the reload does not fix the problem.

3. Clear stale LevelDB LOCK files in the Electron main process before the
   renderer opens IndexedDB. Orphaned locks from unclean session shutdowns
   block the backing store open; safe to remove because requestSingleInstanceLock()
   already confirmed we are the only running instance.

Fixes https://github.com/super-productivity/super-productivity/issues/7191
2026-04-15 18:17:57 +02:00
Johannes Millan
5ce78a5b63 fix(electron): prevent app hang on macOS native quit (Cmd+Q, Dock > Quit)
On macOS, native quit fires before-quit BEFORE window close events. The
before-quit handler was calling ipcMain.removeAllListeners(), which destroyed
the BEFORE_CLOSE_DONE IPC listener. The close handler then sent NOTIFY_ON_CLOSE
to the renderer (for sync/finish-day callbacks), the renderer responded with
BEFORE_CLOSE_DONE, but the listener was gone — causing a permanent hang.

Fix: intercept before-quit when isQuiting=false and delegate to win.close(),
which re-enters the existing close handler and runs before-close callbacks
normally. Move ipcMain.removeAllListeners() to will-quit, which fires after
all windows are closed and all IPC flows are guaranteed complete.

Also improves close event log to include pending before-close handler IDs.
2026-04-15 18:17:57 +02:00
Johannes Millan
46e0fa2d01 fix(sync): add FILE_SYNC_LIST_FILES electron IPC handler and fix preload args
Add missing IPC handler for file listing used by local-file sync, and fix
preload.ts passing bare dirPath string instead of the expected { dirPath }
object — causing IPC handler to destructure undefined.
2026-04-14 22:09:02 +02:00
Johannes Millan
d32f7037a3 fix(sync): preserve own vector clock counter across full-state op resets
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.
2026-04-01 15:41:13 +02:00
Johannes Millan
92e342af68 chore(deps): upgrade electron-dl from v3 to v4 (pure ESM)
electron-dl v4 is a pure ESM package. Since the Electron main process
is compiled with TypeScript "module": "commonjs", static import() gets
transformed to require() which cannot load ESM. Use Function() to
preserve a real dynamic import() at runtime.
2026-03-31 19:40:38 +02:00
Johannes Millan
1ee9d7f566 refactor: rename overlay-indicator to task-widget
Rename the "overlay indicator" feature to "task widget" across the
entire codebase for clearer naming. Includes file renames, config key
migration, IPC event rename, and translation key updates.

Migration handles old persisted data from both the `overlayIndicator`
config key and the deprecated `misc.isOverlayIndicatorEnabled` field.
The `taskWidget` type is made optional in GlobalConfigState to prevent
Typia auto-fix from overwriting migrated values during upgrade.
2026-03-30 22:00:47 +02:00
Johannes Millan
4b0550be35
fix(security): harden Local REST API against DNS rebinding and field injection (#6996)
* fix(security): harden Local REST API against DNS rebinding and field injection

- Add Host header validation to block DNS rebinding attacks from
  malicious websites targeting the localhost API
- Add IS_ELECTRON guard in handler init() for defense-in-depth
- Add allowlist of task fields settable via API (title, notes, isDone,
  timeEstimate, timeSpent, projectId, tagIds, dueDay, dueWithTime,
  plannedAt) to prevent state corruption via injected internal fields
- Validate source query parameter instead of blind cast
- Add tests verifying disallowed fields are stripped from POST and PATCH

https://claude.ai/code/session_01YJNc4gUCkHHrAAhXJBEaop

* fix: address review findings from daily code review

Security hardening:
- Block cloud metadata endpoints (169.254.169.254, metadata.google.internal)
  even when allowPrivateNetwork is true (SSRF protection)
- Add HTTP method allowlist to PluginHttp request() to prevent abuse
- Add rate limiting (max 50 concurrent) to Local REST API

Bug fixes:
- Fix hardcoded 'ICAL' issueProviderKey in calendar effects; use event's
  actual provider key with ICAL fallback
- Fix race condition in TimeBlockDeleteSidecarService: push IDs instead
  of replacing to prevent data loss on rapid bulk deletes
- Backfill missing issueProviderKey on cached calendar events from
  localStorage (backward compatibility)
- Fix btoa(String.fromCharCode(...spread)) overflow for long strings
  by using loop-based approach

Minor:
- Cap HiddenCalendarEventsService to 500 entries max (prevent
  unbounded localStorage growth)
- Fix misleading "deep clone" comment to "shallow clone"

https://claude.ai/code/session_01YJNc4gUCkHHrAAhXJBEaop

* test: fix unit test failures from review hardening changes

- Replace IS_ELECTRON guard with window.ea?.onLocalRestApiRequest check
  in handler init() so tests can mock the API without navigator.userAgent
- Update plugin-http spec to expect "cloud metadata endpoints" error
  message (was "private/local") for 169.254.169.254 and metadata.google
- Fix allowlist test expectation: title is an allowed field and passes
  through pickAllowedFields as expected

https://claude.ai/code/session_01YJNc4gUCkHHrAAhXJBEaop

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-29 11:59:27 +02:00
Onur Neşvat
959004ee18
feat(electron): add Local REST API for desktop automation (#6981)
* feat(electron): add Local REST API for desktop automation

Add a local HTTP server (port 3876) that allows external scripts and
tools to interact with a running Super Productivity desktop app.

Features:
- Task CRUD operations (create, read, update, delete)
- Task control (start, stop, set current)
- Task archive/restore operations
- Query filters for tasks (by title, project, tag, done status)
- Read-only project and tag listing

The API is disabled by default and can be enabled in Settings > Misc.
Only accepts connections from localhost (127.0.0.1) for security.

* fix: resolve lint errors for local REST API

* fix: use lazy injection for LocalRestApiHandlerService

Fixes StartupService tests by using Injector.get() instead of direct
injection, preventing LocalRestApiHandlerService from being instantiated
when StartupService tests don't have NgRx providers.

* fix: resolve markdown lint errors in API docs

* fix: add security warning to Local REST API hint
2026-03-28 13:35:04 +01:00
Johannes Millan
f58c7342fe
fix: address review findings from 18.0.0 release (#6976)
- perf(daily-summary): restore progressive archive loading reverted by cd12556
  Today's tasks render immediately; archive tasks append when ready.
  Show spinner while archive loads, defer confetti until tasks arrive.
- fix(plugins): add 5-minute timeout for abandoned OAuth loopback server
  and simplify shell.openExternal call (already returns Promise)
- fix(color): add vertical overflow check to color picker panel positioning
- fix(issue-panel): fix iCal capitalization in calendar button labels

https://claude.ai/code/session_01SR9JRGdm5wiNw3rKPJdZwY

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-27 11:46:23 +01:00
Johannes Millan
25051d2899 fix(plugins): use loopback redirect and system browser for Google OAuth
Google's Desktop OAuth client requires loopback redirect URIs
(http://127.0.0.1:<port>) and blocks embedded webviews. This replaces
the custom URI scheme + BrowserWindow approach with a temporary loopback
HTTP server and shell.openExternal for the system browser.

- Add PLUGIN_OAUTH_PREPARE IPC to start loopback server and return port
- Open auth URL in system browser instead of Electron BrowserWindow
- Make getRedirectUri() async to support IPC port retrieval
- Validate OAuth config before starting loopback server to prevent leaks
- Force 200 status on OPTIONS preflight responses in CORS bypass
2026-03-26 17:43:04 +01:00
Johannes Millan
c8ac3feda2 fix(lint): resolve prettier formatting and lint errors from CI run 2026-03-24 18:08:30 +01:00
Johannes Millan
6faca760f0
fix: address review findings from daily changes (#6944)
* fix: address review findings from daily changes

- Add setPermissionCheckHandler alongside setPermissionRequestHandler
  for defense-in-depth in Electron (permission queries now also denied)
- Fix tagIds merge ordering in issue service: provider adapter tags are
  now merged with default tags instead of being silently overwritten
- Execute deleteTask action last in automations to prevent subsequent
  actions from failing on a deleted task
- Add unit tests for getTaskDefaults logic (TODAY_TAG filtering,
  defaultNote, tagIds merging, deduplication, context tags)
- Add test for deleteTask execution ordering in ActionExecutor

https://claude.ai/code/session_01UhoR5g7RQgm4E6bZCvU9PS

* refactor: revert YAGNI changes from review fixes

- Revert tagIds merge in issue.service.ts: no provider currently sets
  tagIds in getAddTaskData, so merging solves a hypothetical problem
- Revert deleteTask sort in action-executor: the automations feature is
  new and nobody has hit this edge case yet
- Remove tests for reverted behavior, keep tests for existing features
  (TODAY_TAG filtering, defaultNote, note override prevention)

https://claude.ai/code/session_01UhoR5g7RQgm4E6bZCvU9PS

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-24 12:11:47 +01:00
Johannes Millan
caf85a1b57 fix(electron): enable webSecurity, add permission handler, improve cert logging
Enable webSecurity (same-origin policy) since CORS is already handled at
the session level via onBeforeSendHeaders and onHeadersReceived. Add
setPermissionRequestHandler to deny unnecessary permissions (webcam,
microphone, geolocation). Improve certificate-error logging with warn
level and URL context.
2026-03-23 10:52:43 +01:00
Johannes Millan
a118f65d54 refactor(electron): bundle preload script with esbuild to support sandbox mode
Bundle preload.ts into a single file so all local imports (e.g.
ipc-events.const) are inlined at build time. This allows removing
sandbox: false since the bundled preload only requires the built-in
electron module, which is allowed in sandboxed preloads.
2026-03-22 19:38:21 +01:00
Johannes Millan
815341d5e8 fix(electron): explicitly disable sandbox for preload script compatibility
The preload script uses require() for local modules which is not
supported in Electron's sandboxed preload environment (default since
Electron 20). This only affects Electron's preload API restrictions,
not Chromium's OS-level process sandbox which remains active.
2026-03-22 19:16:32 +01:00
Johannes Millan
02bc3e88e3 feat(two-way-sync): add plugin OAuth, two-way field sync, and remote issue deletion
Add OAuth support for plugins with PKCE, token persistence in local-only
IndexedDB, and Electron/web redirect handling. Extend two-way sync with
dueDay, dueWithTime, and timeEstimate field mappings including mutually
exclusive field clearing. Support remote issue deletion on task delete
and remote deletion detection during polling.

Add Google Calendar plugin (disabled from bundled builds pending legal
review) as a development reference for the plugin OAuth and two-way
sync APIs.

Additional fixes:
- Restore accidentally deleted focus-mode translation keys
- Remove full Task[] from deleteTasks action to prevent op-log bloat
- Add dueDay/deadlineDay format validation guards (#6908)
- Fix Android reminder alarm cancel intent matching
- Validate dueDay format to prevent false overdue from corrupted data
- Fix PKCE test expectation after utility consolidation
2026-03-22 13:02:41 +01:00