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.
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).
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.
* 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
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.
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.
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).
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.
* 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>
* 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
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.
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.
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
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.
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.
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
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
* 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)
* 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, #49173https://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, #49173https://claude.ai/code/session_01JNxazJmDhpMp9UYV6SqnBG
* docs(research): verify peer-app claims, correct SP electron timing
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
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.
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
- 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)
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
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.
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.
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.
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.
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.
* 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>
* 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
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
* 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>
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.
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.
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.
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