Commit graph

19215 commits

Author SHA1 Message Date
Johannes Millan
efaffe97a5 refactor(sync): use discriminated unions for sync orchestrator results
Replace flag-based result objects with DownloadOutcome and UploadOutcome
discriminated unions at the orchestrator level. This eliminates null
returns, optional boolean checks, and ambiguous flag combinations.

- Add DownloadOutcome (5 variants) and UploadOutcome (3 variants) types
- Update OperationLogSyncService to return discriminated unions
- Update SyncWrapperService and ImmediateUploadService consumers
- Use exhaustive switch in downloadCallback adapter
- Replace piggybackedOps array pass-through with piggybackedOpsCount
2026-03-06 12:38:09 +01:00
Johannes Millan
88d57e8e8b docs(sync): replace completed simplification plan with new roadmap
Remove supersync-client-simplification.md (all tasks implemented) and
add sync-core-simplification-roadmap.md for the next phase of work.
2026-03-06 12:37:52 +01:00
Johannes Millan
1f3c6cda9c test(e2e): extract shared encryption warning helper and fix race conditions
- Add handleEncryptionWarningDialog() to supersync-helpers.ts to
  eliminate duplicated dialog-handling blocks across import.page.ts
  and two supersync spec files
- Replace isVisible() point-in-time check with waitFor({ timeout: 1000 })
  in supersync.page.ts to avoid race when dialog is still animating in
- Replace silent .catch(() => {}) with a logged warning when the
  encryption dialog fails to close after confirmation
2026-03-05 21:18:39 +01:00
Johannes Millan
3dc86dd183
Fix/add rate limits (#6745)
* fix(build): fix tag resolution and add error handling in bump-android-version

The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.

Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.

* fix(sync): differentiate auth error messages and clarify token revocation

- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
  with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
  via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
  vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError

Closes #6597

* refactor(sync): use generic auth reason and improve test readability

- Replace 'User not found' and 'Account not verified' with generic
  'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts

* fix(sync-server): add per-route rate limits to silence CodeQL alert

Add explicit rate limits to routes that only had the global rate limit:
- GET /api/sync/status: 60/min
- DELETE /api/sync/data: 3/15min (destructive operation)
- GET /api/sync/restore-points: 30/min
- GET /api/sync/restore/:serverSeq: 10/5min (CPU-intensive)
- GET /reset-password: 20/15min
- GET /recover-passkey: 10/15min

These complement the global @fastify/rate-limit (100/15min) and silence
the CodeQL 'Missing rate limiting' alert on authenticated handlers.
2026-03-05 21:18:36 +01:00
Johannes Millan
7070fde51e
Pr 6741 (#6743)
* fix(build): fix tag resolution and add error handling in bump-android-version

The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.

Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.

* fix(sync): differentiate auth error messages and clarify token revocation

- Rename server dashboard "Refresh Token" to "Revoke & Replace Token"
  with explicit warning that ALL devices will be disconnected
- Return rejection reason in 401 responses (revoked, expired, etc.)
  via discriminated union TokenVerificationResult type
- Show different client error messages for server-side token rejection
  vs missing local credentials to aid user diagnosis
- Extract server error reason from JSON response body in AuthFailSPError

Closes #6597

* refactor(sync): use generic auth reason and improve test readability

- Replace 'User not found' and 'Account not verified' with generic
  'Account unavailable' to avoid leaking account state in API responses
- Extract long reason strings to constants in middleware.spec.ts
2026-03-05 21:17:59 +01:00
Johannes Millan
bdef26c225 refactor(sync): use FULL_STATE_OP_TYPES constant and fix comment numbering 2026-03-05 21:02:26 +01:00
Johannes Millan
755cd705f5
Fix/run 22683814946 (#6740)
test(e2e): fix e2e tests
2026-03-05 19:37:48 +01:00
Johannes Millan
e92eb79db7
Reset vector clocks to minimal after SYNC_IMPORT to drop dead clients (#6738)
* fix(sync): reset vector clock to minimal after SYNC_IMPORT

After a SYNC_IMPORT, working clocks were carrying forward ALL accumulated
client IDs (including dead ones from reinstalls/old browsers), keeping the
clock permanently at MAX_VECTOR_CLOCK_SIZE. This caused unnecessary pruning
and comparison issues on every sync.

Three complementary changes:

1. Reset working clock to minimal after SYNC_IMPORT: When a client creates
   or receives a SYNC_IMPORT, its working clock is reset to only the import
   client's entry + own entry. Dead client IDs are dropped. The full clock
   is preserved in the stored SYNC_IMPORT operation for filtering.

2. Import-client-counter exception in SyncImportFilterService: Post-import
   ops with minimal clocks appear CONCURRENT with the import (missing old
   entries). A new exception recognizes them as post-import by checking if
   the op has the import client's counter >= the import's own counter value.

3. Updated SimulatedClient test helper to reset clocks on SYNC_IMPORT,
   matching the real behavior in OperationLogStoreService and
   SyncHydrationService.

https://claude.ai/code/session_01T5K1kq8m6LPDc6dEGyXnEW

* chore: update package-lock.json

https://claude.ai/code/session_01T5K1kq8m6LPDc6dEGyXnEW

* refactor(sync): use FULL_STATE_OP_TYPES constant and improve test assertions

- Replace magic string casts `(opType as string) === 'REPAIR'` with
  `FULL_STATE_OP_TYPES.has(opType)` in simulated-client helper
- Document transitive propagation assumption in import-client-counter
  exception comment
- Improve test assertion to use toContain for better failure messages

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-05 19:19:51 +01:00
Johannes Millan
0ea0ba143f fix(electron): improve protocol URL handling and register URL scheme on Linux
Suppress noisy "EXITING due to failed single instance lock" message when
a second instance is launched via xdg-open for protocol URL handling.
Register superproductivity:// as a MIME type handler in the .desktop file
so Linux users no longer need manual xdg-settings configuration.
Remove duplicate requestSingleInstanceLock() call in start-app.ts.

Closes #173
2026-03-05 16:45:05 +01:00
Johannes Millan
49d24bfec0 fix(sync): fix encryption key loss on revert and isSubTask detection in LWW update
- Pre-capture provider config before destructive deleteAndReuploadWithNewEncryption
  call so disableEncryption revert restores the original encryption key
- Merge guard check and config pre-capture in enableEncryption to eliminate
  redundant getActiveProvider()/privateCfg.load() calls
- Fix isSubTask detection in LWW meta-reducer to use new parentId when present,
  correctly handling subtask-to-main-task promotion
- Remove redundant unique() calls guarded by preceding includes() checks
2026-03-05 16:45:05 +01:00
Johannes Millan
8fe938a5f8 fix(build): fix tag resolution and add error handling in bump-android-version
The previous two-tag approach (`currentTag` → `prevTag`) would resolve the
wrong changelog range during `npm version` since the new tag doesn't exist
yet. Simplified to a single `lastTag...HEAD` range which correctly captures
all commits since the last release.

Added try-catch with fallback to last 20 commits when no tags exist, and
a fallback message for empty changelogs.
2026-03-05 16:45:05 +01:00
dependabot[bot]
d094881f73
chore(deps): bump the npm_and_yarn group across 1 directory with 4 updates (#6736)
Bumps the npm_and_yarn group with 4 updates in the / directory: [ajv](https://github.com/ajv-validator/ajv), [dompurify](https://github.com/cure53/DOMPurify), [immutable](https://github.com/immutable-js/immutable-js) and [tar](https://github.com/isaacs/node-tar).


Updates `ajv` from 8.17.1 to 8.18.0
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0)

Updates `dompurify` from 3.2.5 to 3.3.2
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.5...3.3.2)

Updates `immutable` from 5.1.4 to 5.1.5
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5)

Updates `tar` from 7.5.9 to 7.5.10
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.9...v7.5.10)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: dompurify
  dependency-version: 3.3.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: immutable
  dependency-version: 5.1.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: tar
  dependency-version: 7.5.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 16:35:34 +01:00
overcuriousity
95883a68a7
fix #6684 (#6732) 2026-03-05 14:18:13 +01:00
dependabot[bot]
bb9c3bc614
chore(deps): bump the npm_and_yarn group across 1 directory with 4 updates (#6730)
Bumps the npm_and_yarn group with 4 updates in the / directory: [ajv](https://github.com/ajv-validator/ajv), [@hono/node-server](https://github.com/honojs/node-server), [hono](https://github.com/honojs/hono) and [rollup](https://github.com/rollup/rollup).


Updates `ajv` from 6.12.6 to 6.14.0
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

Updates `@hono/node-server` from 1.19.9 to 1.19.10
- [Release notes](https://github.com/honojs/node-server/releases)
- [Commits](https://github.com/honojs/node-server/compare/v1.19.9...v1.19.10)

Updates `hono` from 4.12.0 to 4.12.5
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.12.0...v4.12.5)

Updates `rollup` from 4.52.3 to 4.59.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.52.3...v4.59.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: "@hono/node-server"
  dependency-version: 1.19.10
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: hono
  dependency-version: 4.12.5
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: rollup
  dependency-version: 4.59.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 13:44:57 +01:00
Johannes Millan
9f014dc3b8 chore: gitignore .claude/ directory and fix focus-mode test
Ignore entire .claude/ directory to keep local settings and commands
out of version control. Also fix test expectation to match Bug #6726
behavior where pausedTaskId is intentionally undefined on skipBreak.
2026-03-04 19:38:37 +01:00
Johannes Millan
46d9e27d66 fix(tasks): prevent right sidebar from opening after closing focus session
Remove unnecessary setSelectedId call from goToFocusMode() in the task
context menu. This aligns behavior with other focus mode entry points
(keyboard shortcut, header button) which only set currentTaskId.

Closes #6727
2026-03-04 19:38:37 +01:00
Johannes Millan
1388dfe1b5 fix(focus-mode): allow switching tasks during Pomodoro breaks (#6726)
Don't override user's task choice when a break ends or is skipped.
Guard setCurrentTask dispatch with currentTaskId check in skipBreak$
and resumeTrackingOnBreakComplete$ effects, and pass undefined
pausedTaskId when syncTrackingStartToSession$ auto-skips a break.
2026-03-04 19:38:37 +01:00
Johannes Millan
1272d6e7bf chore: replace CHANGELOG.md with git-based release notes
Remove the 15k-line auto-generated CHANGELOG.md and its conventional-changelog
tooling. The bump-android-version script now derives Play Store fastlane
changelogs from git log between tags instead of parsing CHANGELOG.md.

- Use execFileSync with argv to avoid shell injection
- Filter merge commits with --no-merges
- Handle breaking-change prefix (feat!:) in regex
- Truncate at line boundaries for 500-char Play Store limit
- Remove conventional-changelog-cli and @conventional-changelog/git-client deps
- Remove release.changelog script from package.json
2026-03-04 19:38:37 +01:00
Johannes Millan
7164a5e44b test(e2e): fix encryption button timing and drag-handle strict mode violation
Two root causes for failing E2E tests in CI:

1. Encryption button not visible after provider selection: The async
   provider change listener loads config from IndexedDB and updates the
   Formly model, but the 1000ms waitForTimeout was insufficient in CI.
   Replace with polling (200ms intervals, 10s timeout) that waits for
   either enable or disable encryption button to become visible.
   Also removes ~130 lines of leftover debug logging from disableEncryption().

2. Drag-handle strict mode violation on parent tasks with subtasks:
   task.locator('.drag-handle') matched both the parent's drag handle
   and nested subtask drag handles. Add .first() to all drag-handle
   locator usages to target only the parent task's own handle.
2026-03-04 19:38:37 +01:00
Johannes Millan
ef07ae8ff2 fix(android): make sidebar settings button visible above system nav bar
Use JS-computed var(--safe-area-bottom) instead of env(safe-area-inset-bottom)
which returns 0 on Android WebView. Also account for bottom nav height and
position sidebar below status bar on native mobile.

Closes #6561
2026-03-04 19:38:37 +01:00
Johannes Millan
1471b101fe docs: add SignPath code signing acknowledgment to README 2026-03-04 19:38:37 +01:00
Johannes Millan
49bf056ee0
refactor(sync): simplify SuperSync client code (#6728)
- Unify duplicated HTTP methods in SuperSyncProvider into shared helpers
- Remove 5 encryption DI injection tokens, use private properties for test spying
- Remove DerivedKeyCacheService wrapper, use direct imports
- Cache _getServerSeqKey() result in SuperSyncProvider
- Split SyncProviderServiceInterface into SyncProviderBase + FileSyncProvider
- Remove dead file-op stubs from SuperSync provider
- Extract shared delete-and-reupload pattern into SnapshotUploadService
- Extract helper methods from operation-log-sync.service.ts
- Auto-invalidate WrappedProviderService cache on config changes
- Fix: preserve auth credentials in encryption toggle revert paths
- Fix: add config revert on disableEncryption failure

~720 lines net reduction across 26 files.
2026-03-04 19:01:20 +01:00
Johannes Millan
7c30916d28 test: fix 4 failing unit tests after recent sync and dueDay changes
- Add TODAY_TAG to FAKE_ROOT_STATE in TaskArchiveService so
  handleUpdateTask can find it when marking tasks as done
- Update dueDay assertion to expect defined value (set to today
  by updateDoneOnForTask)
- Add mock tasks to LWW integration tests so orphan filter
  doesn't remove taskIds from PROJECT/TAG entities
2026-03-04 13:16:01 +01:00
Johannes Millan
b83c6be7fa fix(sync): filter orphaned taskIds from tags and projects during sync
LWW conflict resolution replaces entire TAG/PROJECT entities without
checking whether referenced taskIds still exist. Similarly, updateTag
actions from remote clients can re-introduce deleted task IDs. This
adds active filtering in both the lwwUpdateMetaReducer (for LWW Update
actions) and tagSharedMetaReducer (for updateTag actions) to remove
references to non-existent tasks before they enter the store.
2026-03-04 12:53:44 +01:00
Johannes Millan
7293b076ac Merge branch 'fix/issue-6715'
* fix/issue-6715:
  fix(focus-mode): remove isManualSessionCompletion to fix timer running past 0
2026-03-04 11:48:13 +01:00
Johannes Millan
a4c37494e8 Merge branch 'fix/issue-6716'
* fix/issue-6716:
  fix(tasks): set dueDay to today when marking task as done
  build: update types map
2026-03-04 11:47:55 +01:00
Johannes Millan
48daba870a Merge branch 'fix/startup-error-mobile'
* fix/startup-error-mobile:
  fix(sync): make dataRepair return accurate fix counts in RepairSummary
2026-03-04 11:47:46 +01:00
Johannes Millan
2789d6c140 fix(tasks): set dueDay to today when marking task as done
When a task is completed, set dueDay to today's date instead of clearing
it, so completed tasks appear in the Today view and metrics. Also clears
dueWithTime for mutual exclusivity and adds the task to TODAY_TAG.taskIds
for ordering. Subtasks are excluded from TODAY_TAG.taskIds since their
parent manages membership. Completed tasks are also removed from future
planner days to prevent stale entries.

Closes #6716
2026-03-04 11:47:37 +01:00
Johannes Millan
a73b609d7f fix(focus-mode): remove isManualSessionCompletion to fix timer running past 0
When isManualBreakStart was enabled, the isManualSessionCompletion flag
prevented the tick reducer from stopping the timer at duration, causing
it to run past 0 indefinitely. Remove this flag entirely — break
auto-start is already correctly gated by isManualBreakStart in the
autoStartBreakOnSessionComplete$ effect.

Fixes #6715
2026-03-04 11:46:23 +01:00
Johannes Millan
4747df70b1 fix(sync): make dataRepair return accurate fix counts in RepairSummary 2026-03-04 11:38:23 +01:00
Johannes Millan
91098005a9 build: update types map 2026-03-04 11:25:28 +01:00
Johannes Millan
30cbb4d7a6
fix(build): ensure plugin-api is built before plugin builds (#6720)
* fix(build): ensure plugin-api is built before plugin builds

The github-issue-provider plugin build was failing in CI because
plugin-api/dist/ didn't exist when tsc --noEmit ran. The dist/ directory
is gitignored and only built during the prepare lifecycle, which may not
run reliably in all CI contexts. Adding plugin-api:build to the
plugins:build script ensures the type declarations are always available.

Also improved error logging in build-all.js to show stderr on failure.

https://claude.ai/code/session_01StB1pMk1g2k79AywyksoyZ

* fix(build): remove tsc --noEmit from github-issue-provider build

The github-issue-provider was the only plugin running tsc --noEmit as
part of its build command. In CI, TypeScript module resolution fails for
the file: dependency on plugin-api due to npm workspace hoisting
interference. All other plugins (ai-productivity-prompts,
procrastination-buster, sync-md) skip type-checking during build and
rely on esbuild to strip type-only imports.

Align with existing plugin pattern by separating build from typecheck.
Also improve error logging in build-all.js to capture stdout on failure.

https://claude.ai/code/session_01StB1pMk1g2k79AywyksoyZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-04 11:25:21 +01:00
Johannes Millan
1ecebda2c2 build: update types map 2026-03-04 10:42:25 +01:00
Johannes Millan
6394fa4363
Claude/fix failing e2e tests ov lxs (#6721)
* test(e2e): fix SuperSync encryption overlay and plugin build in CI

Two root causes for failing e2e tests:

1. Plugin build failure in global-setup.ts: The plugin manifest check
   only looked at the dev path (src/assets/bundled-plugins/) but not the
   CI pre-built path (.tmp/angular-dist/browser/assets/bundled-plugins/).
   In CI, the frontend is pre-built and downloaded as an artifact, so the
   dev path doesn't exist. This caused all WebDAV tests to fail.

2. SuperSync enableEncryption overlay blocking: The sync wrapper opens
   a mandatory encryption dialog with disableClose:true after a successful
   sync when encryption is not yet configured. ensureOverlaysClosed()
   uses Escape key which cannot dismiss disableClose dialogs.

   Fixes:
   - Add enableEncryptionDialog to the sync outcome race so the mandatory
     dialog is detected immediately instead of waiting for a 30s timeout
   - enableEncryption() now checks if the dialog is already open and
     handles it directly instead of trying to right-click the sync button
   - Extract _fillAndConfirmEncryptionDialog() and
     _waitForEncryptionSyncComplete() helpers for reuse
   - Improve enable encryption button lookup to check both top-level
     (SuperSync) and Advanced collapsible positions

https://claude.ai/code/session_01CnQXCAB3WYnYdKPbNEqUS3

* chore: update package-lock.json after npm install

Regenerated lock file to resolve workspace dependencies including
vite-plugin's devDependencies (@types/node, vite, typescript).

https://claude.ai/code/session_01CnQXCAB3WYnYdKPbNEqUS3

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-04 00:59:02 +01:00
Johannes Millan
af373e4e5b fix(plugins): add missing providers to plugin-bridge-counter spec
Add mock providers for GlobalThemeService, PluginIssueProviderRegistryService,
IssueSyncAdapterRegistryService, and PluginHttpService to prevent transitive
dependency resolution reaching LayoutService which requires Store.pipe().
2026-03-03 22:05:46 +01:00
Johannes Millan
2281a5b3b5 test(plugins): add missing providers to PluginBridgeService spec
Add GlobalThemeService, PluginIssueProviderRegistryService,
IssueSyncAdapterRegistryService, and PluginHttpService mock providers
to fix NG0201 injection errors in counter method tests.
2026-03-03 22:00:23 +01:00
Johannes Millan
68a2d8a9b7 test(e2e): fix flaky tests with better waits and focus handling
Replace unreliable networkidle waits with explicit element/URL waits,
improve keyboard focus handling with retry logic, fix strict mode
violations, and add hover before note menu interactions.
2026-03-03 21:46:11 +01:00
Johannes Millan
1959822408 test(focus-mode): add missing Store provider to pomodoro settings spec 2026-03-03 21:15:45 +01:00
Johannes Millan
9af48db7ef fix(sync): wrap @else content in ng-container for MatButton icon projection 2026-03-03 20:43:05 +01:00
Johannes Millan
f999f7d279 fix(plugins): fix class field init order crash in issue provider dialog
Move title declaration before configFormSection and convert
_mapPluginConfigField from arrow function field to method so both
are available when _getPluginFormSection() runs during field init.
2026-03-03 20:39:27 +01:00
Johannes Millan
7792ad10ba fix(plugins): fix TS2352 type cast in issue provider migration 2026-03-03 20:32:47 +01:00
Johannes Millan
7c746fb48d fix(e2e): disable reuseExistingServer to prevent stale server issues
With reuseExistingServer: true, Playwright silently reuses whatever
is on port 4242 without health checks. A stale ng serve that no
longer serves assets causes all tests to fail with cryptic 404s.
Setting false makes Playwright fail fast with a clear error message.
2026-03-03 20:31:37 +01:00
Johannes Millan
855079b353 test(e2e): fix skip break button selector in pomodoro tests
The regex /skip|start/i matched "restart_alt" (mat-icon text in
the "Reset session counter" button) before "Skip break", causing
the wrong button to be clicked and the break screen to stay visible.
2026-03-03 20:14:54 +01:00
Johannes Millan
e7c8940b10 fix(plugins): fix TS errors and build for iFrame-less plugins
- Fix type errors in dialog-edit-issue-provider component by casting
  map result to LimitedFormlyFieldConfig and importing the type
- Fix type cast in issue-provider reducer migration via unknown
- Make build-packages.js only require index.html for plugins with
  iFrame enabled, checking manifest.json in both root and src/
2026-03-03 20:14:54 +01:00
Johannes Millan
0a1fc28067 fix(sync): preserve SuperSync encryption config during form saves
The Formly form model defaults isEncryptionEnabled to false. When
_updatePrivateConfig() merges form values over the saved config,
this overwrites the true value set by the EnableEncryption dialog.
Every subsequent sync then triggers _promptSuperSyncEncryptionIfNeeded(),
re-opening the encryption dialog and causing DecryptNoPasswordError
on other clients.

For SuperSync, isEncryptionEnabled is now preserved from the saved
config since it is managed exclusively by dedicated dialogs
(EnableEncryption, DisableEncryption, HandleDecryptError).

Also hardens the E2E page object to handle encryption dialogs that
appear mid-sync (enter_password, enable_encryption) using a stored
password from setupSuperSync().
2026-03-03 20:14:54 +01:00
Johannes Millan
6a78913177 feat(plugins): add plugin issue provider system with GitHub migration
Add a plugin-based issue provider architecture that allows plugins to
register as issue providers alongside the existing built-in providers.

- Plugin API: types for search, display, comments, two-way sync, and
  field mappings in @super-productivity/plugin-api
- Registry service to manage plugin provider lifecycle
- HTTP proxy with SSRF protection for plugin network requests
- Adapter service implementing IssueServiceInterface for plugins
- Sync adapter for plugin-based two-way sync
- Plugin config UI in the issue provider edit dialog
- Plugin providers shown in setup overview and issue panel

Migrate GitHub from built-in to plugin as first proof:
- Bundled github-issue-provider plugin with full feature parity
- Reducer migration converts old GitHub data to plugin format while
  preserving legacy fields for cross-version compatibility
- Auto-enable plugin when existing GitHub providers are detected
- Plugin registers under 'GITHUB' key via MigratedIssueProviderKey
- GitHub translations moved to plugin i18n bundles

Mark Two-Way Sync as experimental in the UI.
2026-03-03 20:14:54 +01:00
Johannes Millan
2eaef79d21 fix(sidebar): account for safe area insets on native mobile iPad #6711
On native mobile (Capacitor), body padding for safe areas pushed the
app container below the visible viewport, clipping the sidebar bottom
and hiding items like Settings. Subtract safe area insets from the
container height and let the sidebar inherit it.
2026-03-03 20:14:54 +01:00
Johannes Millan
06113152c8 feat(i18n): add CLDR-aware plural form support via Intl.PluralRules
Add getPluralKey() utility that uses the browser-native Intl.PluralRules
API to select the correct CLDR plural category (one/few/many/other) for
any locale. This enables languages like Romanian, Russian, Polish, and
Arabic to provide proper plural forms beyond simple singular/plural.

Key changes:
- New getPluralKey() utility with locale-aware fallback via TranslateStore
- Rename translation keys: SINGULAR→ONE, PLURAL→OTHER (CLDR convention)
- Nest banner TXT_MULTIPLE keys into ONE/OTHER for proper English grammar
- Migrate all 27 locale files to new key structure
- Add 10 unit tests covering CLDR categories, fallback, and edge cases

Closes discussion #6698
2026-03-03 20:14:54 +01:00
Johannes Millan
94e8e8a0a9 fix(focus-mode): add reset button for pomodoro session counter #6709
Wire the existing but unused resetCycles NgRx action to two UI
locations so users can reset the pomodoro session counter:
- Break screen: shown only in Pomodoro mode alongside skip/planning
- Pomodoro settings dialog: left-aligned reset button in actions row
2026-03-03 20:14:54 +01:00
Johannes Millan
83ae3f0abe fix(sync): remove skip waiting for sync button
The button appeared after 3s but an 8s auto-timeout already exists
as a safety net. For SuperSync, there's no wait at all. The button
provided marginal value and created unnecessary UI noise.
2026-03-03 20:14:54 +01:00