Commit graph

20587 commits

Author SHA1 Message Date
Johannes Millan
3fbde60742 refactor(supersync): address round-2 review of WS reconnect cooldown fix
- Replace the `client.refusedChallengers = 0` post-summary reset with a
  dedicated `summaryLogged: boolean` flag. The reset muddied the
  field's documented "lifetime count" semantics for any future reader
  observing the closing socket. The flag dedupes the `ws.on('close')`
  re-entry without lying about the count.
- Refresh the stale `RECONNECT_COOLDOWN_MS` doc that still described
  the non-sliding semantics from before fbdedf597e.
- Short-circuit the WS-429 path normalize on `statusCode === 429` in
  setErrorHandler so the split+replace doesn't run on the 99%+ of
  error responses that aren't 429.
- Drop the redundant `cid.length > 0` guard in `isValidClientId` —
  the trailing regex already requires `+` (≥1 char).
- Route the websocket.routes spec simulator through `isValidClientId`
  so it can't drift from production validation order again.
- Add a heartbeat-path regression test for the storm-summary dedup
  (the explicit-eviction path was already covered by fbdedf597e).
2026-05-20 15:57:00 +02:00
Johannes Millan
0c70e7906f fix(supersync): make WS reconnect cooldown sliding and quiet the storm logs
The 5s cooldown introduced in ec1f09c85b kept the incumbent socket from
being evicted by a single challenger, but the gate was anchored to the
incumbent's connectedAt — so after 5s of a sustained pre-18.6.0
reconnect storm, the next challenger evicted the incumbent and the
cycle restarted every ~5s. Production logs confirmed: the same clientId
appeared in both "Reconnect within cooldown" WARNs and "Replacing stale
connection" INFOs within ~200ms.

Make the cooldown a sliding window via a single cooldownUntil field:
each refused challenger pushes it forward, so eviction only resumes
after RECONNECT_COOLDOWN_MS of quiet (no challengers). Sustained storm
keeps the incumbent forever; genuine network-blip reconnect after the
storm subsides still recovers.

Cut the per-attempt log spam:
- First refusal per incumbent emits the WARN; subsequent refusals are
  silently counted on the ConnectedClient.
- On incumbent removal, log a single INFO summary with the total count
  and incumbent lifetime. Zero the counter after logging so the
  inevitable ws.on('close') re-entry (triggered by removeConnection's
  own ws.close) cannot double-fire the summary.
- WS-route 429s downgraded to debug in setErrorHandler (storm tail, not
  actionable). Exact-match on /api/sync/ws (path + query strip) so
  future sibling routes do not silently inherit debug level.

Stop one bad client from draining shared-NAT quota: the WS route's
rate-limit keyGenerator now keys on \${ip}:\${clientId} instead of ip
alone. Per-IP amplification stays bounded by the server-wide 500/15min
cap. Extract isValidClientId into sync.const so the keyGenerator and
the route handler share one definition; length check runs before the
regex to reject multi-MB clientId probes cheaply.

Tests cover sliding window under sustained storm, one-WARN +
summary-on-removal, no-summary on clean close, and the
double-summary regression on the explicit-eviction path.
2026-05-20 15:57:00 +02:00
Johannes Millan
6aa28711d1 fix(tasks): keep panel open when toggling from another task (#7694)
The focusin auto-switch added in #6578 set selectedTaskId to the clicked
task before the toggle button's click handler ran, making isSelected()
true and inverting the toggle to close. Skip the auto-switch when focus
came from the toggle button itself — the click handler owns that action.
2026-05-20 15:57:00 +02:00
Het Savani
fab5d15cfa
feat(focus-mode): auto-start break after manual session completion (#7681)
* feat(focus-mode): auto-start break after manual session completion

* refactor(focus-mode): clean up manual break flow and update overtime semantics

* refactor(focus-mode): revert unintended Flowtime auto-break behavior

* docs(focus-mode): restore offerFlowtimeBreakOnSessionEnd$ rationale comment

The v3 revert removed the comment block documenting why this effect
listens to endFlowtimeSession rather than pauseFocusSession (which is
fired by sync-stop, idle, and the regular pause button). Restoring it
to match master.

---------

Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
2026-05-20 15:47:06 +02:00
Johannes Millan
0df62d6beb test(tasks): isolate clipboard mock in attachment spec 2026-05-20 13:28:23 +02:00
dependabot[bot]
f8317929dd
chore(deps): bump vitest from 3.2.4 to 4.1.6 (#7687)
Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.2.4 to 4.1.6.
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.6/packages/vitest)

---
updated-dependencies:
- dependency-name: vitest
  dependency-version: 4.1.6
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 12:50:58 +02:00
dependabot[bot]
9f27d04056
chore(deps): bump @xmldom/xmldom from 0.8.12 to 0.9.10 (#7691)
Bumps [@xmldom/xmldom](https://github.com/xmldom/xmldom) from 0.8.12 to 0.9.10.
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.12...0.9.10)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.9.10
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-20 12:50:37 +02:00
Corey Newton
441952f0ed
chore(deps): add cooldown for NPM and GH Actions to reduce supply chain attack risk (#7685)
- Monthly/weekly update frequency reduces the window of exposure but the addition of a cooldown should help narrow it further.
- Implemented as suggested in this gist: https://gist.github.com/mcollina/b294a6c39ee700d24073c0e5a4e93104
- I've opted to use the more conservative 3-day cooldown as this is the default in some tools.
- Dependabot needs to be adjusted in combination with .npmrc to avoid creating PRs that can't succeed.

Ref.
https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#cooldown-
2026-05-20 11:50:30 +02:00
johannesjo
afe3514818 fix(focus-mode): hide mode selector once active session is in Flowtime
Once a session is switched to Flowtime mid-run, only the Flowtime button
remained — a no-op single-button selector. The breathing dot and count-up
clock already convey the active mode, and users can't switch back to a
fixed-duration mode mid-session.
2026-05-19 23:24:38 +02:00
johannesjo
64c622fab1 feat(tasks): copy focused task title with Ctrl/Cmd+C
Hardens the shortcut: blocks alt/shift modifiers, skips when the
event target is an input/textarea/contenteditable or text is
selected, and guards against the focus-recovery path copying a
stale title. Matches on `ev.code === 'KeyC'` so the shortcut still
fires on non-Latin layouts, mirroring the browser's native copy.
2026-05-19 22:29:41 +02:00
johannesjo
8173559c6c fix(planner): ignore 24h calendar events in budget 2026-05-19 22:29:41 +02:00
Johannes Millan
174fd364e7 fix(supersync): pin incumbent WS socket to break shared-clientId reconnect storm
Pre-18.6.0 clients reconnect immediately on the 4009 eviction. With a
per-origin clientId reused across rapid reconnects, addConnection evicted
the incumbent and accepted the challenger, emitting 4009 every cycle ->
self-sustaining reconnect storm (observed ~32 evictions/sec fleet-wide,
each triggering a client download, saturating the VM).

Add a 5s reconnect cooldown: if a still-OPEN socket for the clientId was
accepted < RECONNECT_COOLDOWN_MS ago, refuse the challenger (4008) and
keep the incumbent untouched. The incumbent is never evicted, so the
server stops emitting 4009 and the loop loses its fuel. A dead/closing
incumbent bypasses the cooldown, so a genuine network-blip reconnect
still recovers.
2026-05-19 17:34:47 +02:00
Johannes Millan
7f7500010a docs(supersync): document DATABASE_URL connection_limit requirement
Prisma's default pool is host-core-scaled and silently consumes the
entire postgres max_connections cap. Document the required
?connection_limit=N&pool_timeout=20 in env.example so operators and
deploy.sh keep the bound (host .env is not in the repo).
2026-05-19 17:34:47 +02:00
Johannes Millan
57ed3c94d0 fix(supersync): raise postgres max_connections to 120 for pool headroom
Prisma's default connection pool is ~(host_cpu_count * 2), read from host
cores not the container cpu limit. On the production VPS that silently
consumed the entire postgres max_connections=40 cap, leaving zero
headroom for psql, migrations, the old-ops cleanup job, or the reconnect
stampede after an in-place crash recovery -> sustained 'sorry, too many
clients already', WS reconnect storms and 429s.

Raise max_connections to 120 (120 x 4MB work_mem + 384MB shared_buffers
stays well inside the 1.5g mem_limit). The app must also bound its pool
via DATABASE_URL ?connection_limit=60 (host .env, not in repo).
2026-05-19 16:42:46 +02:00
Johannes Millan
bc08cfc094 fix(sync): distinguish 'data synced' from 'already in sync' message
_sync() returned SyncStatus.InSync for every successful sync, so the
sync button always showed "Already in sync" even when ops were
downloaded or uploaded (discussion #7196). Compute whether the sync
moved any data from the existing download/upload results and return
SyncStatus.UpdateRemote when it did, InSync only when nothing changed.
Reuses existing enum + translated strings; no new i18n key or snackbar.

Broaden the post-sync SuperSync encryption-prompt guard from
'=== InSync' to '\!== HANDLED_ERROR' so it still fires after a sync
that changed data.
2026-05-19 16:42:45 +02:00
Johannes Millan
79133f9046 feat(electron): suffix version string per distribution target
Append a short channel marker to the (display-only) version string so bug
reports and the config footer reveal which build a user runs, e.g.:

  win nsis W   portable P   MS Store MS
  mac dmg  D   App Store MAS
  linux AppImage AI   snap SN   flatpak FP   deb/rpm L
  android Play A   F-Droid AF   ios I   web WB

Desktop channels are detected in the Electron process (process.platform,
process.mas/windowsStore, APPIMAGE/SNAP/FLATPAK_ID) via a shared helper
reused by the tray-GUID logic and exposed sync over the preload bridge
(no IPC, getAppVersionStr stays synchronous). Mobile/web are detected in
the frontend. deb vs rpm is not distinguishable at runtime, so both
report L. No caller semver-compares this string.
2026-05-19 16:42:45 +02:00
Johannes Millan
e58dc04179 fix(i18n): correct stale Schedule button translations (#7669)
PR #6194 changed en ADD_TASK_BAR.DUE_BUTTON value Due->Schedule but
left all locale files untranslated. The i18n script only fills
empty values, so 18 locales kept showing the old due/deadline word
(it: Scadenza, de: fällig, etc.). Retranslate DUE_BUTTON to mean
schedule/plan in those 18 locales.
2026-05-19 16:42:45 +02:00
Johannes Millan
4124a83df5 fix(build): restore app-builder-lib minimatch pin in lockfile
PR #7650 (b80e81f3e3) regenerated package-lock.json with npm that did
not honor the app-builder-lib minimatch override, reverting it from
10.1.1 to 3.1.5/5.1.9/9.0.9. This desynced the lockfile from
package.json, breaking every `npm ci` build (SuperSync Docker image,
root Dockerfile, plugin-tests workflow).

Regenerate lockfile with npm install --package-lock-only so the
overrides block is applied again. Verified with npm ci --dry-run.
2026-05-19 16:42:45 +02:00
BioWare
9f083a1f76
feat(habit-tracker): grey out non-selected days in specific-days streak (#7646)
* feat(habit-tracker): grey out non-selected days in specific-days streak mode

* refactor(habit-tracker): optimize date parsing and fix streak mode logic

* feat(habit-tracker): fix disabled day interactions and add tests
2026-05-19 16:01:36 +02:00
Corey Newton
4889a5a2ea
chore: limit "resolved" actions to correct categories (#7679)
GH Action will incorrectly mark discussions without the question-answer
format as answered when that is not possible. The action should be fixed
but restricting to the correct categories will also avoid this problem.
2026-05-19 15:55:29 +02:00
Het Savani
fb0f4bc747
feat(focus-mode): show flowtime and countdown timer in browser tab ti… (#7640)
* feat(focus-mode): show flowtime and countdown timer in browser tab title and removes app name during timer

* fix(focus-mode): correct flowtime break browser title behavior
2026-05-19 15:01:09 +02:00
agncr
71787a199f
feat(nav): add keyboard shortcut to toggle sidebar compact/full mode (#7659)
* feat(nav): add keyboard shortcut to toggle sidebar compact/full mode

Ctrl+B toggles the sidebar between compact and full mode, matching
VS Code's "Toggle Primary Side Bar Visibility". Uses the existing
signal-trigger pattern (LayoutService -> MagicSideNavComponent effect).
Shortcut is configurable in Settings > Keyboard Shortcuts.

* fix(nav): prevent spurious sidebar toggle on viewport resize

---------

Co-authored-by: agncr <agncr@users.noreply.github.com>
2026-05-19 15:00:14 +02:00
Johannes Millan
ea9ed90258 fix(plugin-automations): guard nullish currentTaskChange payload
The currentTaskChange handler destructured { current, previous } from
the hook payload with no nullish guard, unlike its sibling handlers
(taskCreated/taskComplete/taskUpdate) which all check payload first.
A nullish payload threw TypeError into PluginHooksService.dispatchHook,
logged as a plugin hook handler error. Guard the payload before
destructuring, matching the existing handler pattern.
2026-05-18 21:59:25 +02:00
Johannes Millan
61d5cba934 fix(build): remove sharp from deps to fix F-Droid build
sharp is a native module that fails in F-Droid's offline/restricted
build environment. The prior partial fix only moved it to
optionalDependencies but left it in package-lock.json, so the build
still pulled it in. Remove sharp from package.json and the lockfile
entirely (replicating the proven #6637 fix), and lazy-install it on
demand only in the dev-only marketing-screenshot scripts that need it.

Closes #7542
2026-05-18 21:59:24 +02:00
Het Savani
b80e81f3e3
feat(tasks): auto add tasks with deadlines today to Today view (#7650)
* feat(tasks): auto add tasks with deadlines today to Today view

* fix(tasks): preserve schedule when completing tasks

* fix(tasks): cover scheduled completion edge cases

* chore(deps)(deps): bump cloudflare/wrangler-action from 3.15.0 to 4.0.0 (#7653)

Bumps [cloudflare/wrangler-action](https://github.com/cloudflare/wrangler-action) from 3.15.0 to 4.0.0.
- [Release notes](https://github.com/cloudflare/wrangler-action/releases)
- [Changelog](https://github.com/cloudflare/wrangler-action/blob/main/CHANGELOG.md)
- [Commits](9acf94ace1...ebbaa15849)

---
updated-dependencies:
- dependency-name: cloudflare/wrangler-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps)(deps): bump the github-actions-minor group with 4 updates (#7652)

Bumps the github-actions-minor group with 4 updates: [step-security/harden-runner](https://github.com/step-security/harden-runner), [browser-actions/setup-chrome](https://github.com/browser-actions/setup-chrome), [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `step-security/harden-runner` from 2.19.1 to 2.19.3
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](a5ad31d6a1...ab7a9404c0)

Updates `browser-actions/setup-chrome` from 2.1.1 to 2.1.2
- [Release notes](https://github.com/browser-actions/setup-chrome/releases)
- [Changelog](https://github.com/browser-actions/setup-chrome/blob/master/CHANGELOG.md)
- [Commits](4f8e94349a...2e1d749697)

Updates `anthropics/claude-code-action` from 1.0.111 to 1.0.123
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](fefa07e9c6...51ea8ea73a)

Updates `github/codeql-action` from 4.35.3 to 4.35.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e46ed2cbd0...9e0d7b8d25)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.19.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: browser-actions/setup-chrome
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.123
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix(plugins): allow iframe-only plugin installs

* fix(config): validate start of next day boundary

* fix(sync): support separate Nextcloud login name

Refs #7617

* test(tasks): add scheduled completion round trips

* fix(focus-mode): allow active timers to switch to flowtime

* test(tasks): expand completion replay matrix

* perf(sync): use indexed findFirst for op-download minSeq

Prisma operation.aggregate({ _min: { serverSeq } }) compiles to MIN()
over a `SELECT ... OFFSET 0` subquery. The OFFSET is a Postgres planner
optimization fence, so the (user_id, server_seq) index could not serve a
first-row seek and the query degraded to a per-user O(N) scan. Under a
client reconnect stampede this ran minutes inside the 60s interactive
download transaction, blowing the tx timeout (500s) and exhausting the
connection pool (cascading upload+download failures).

Replace it with findFirst ordered by serverSeq asc, which compiles to
ORDER BY server_seq ASC LIMIT 1 — a guaranteed index seek on the existing
unique (user_id, server_seq) index. Behaviour-preserving: same minSeq
value and same null-when-empty semantics.

Update both consuming specs (operation-download.service, gap-detection)
to the two-findFirst-call shape and add a regression test asserting the
indexed query is used and the aggregate path is not reintroduced.

* fix(supersync): use 127.0.0.1 in caddy healthcheck to avoid ::1 refusal

busybox wget in caddy:2.11-alpine resolves `localhost` to ::1 first, but Caddy binds its admin API to IPv4 127.0.0.1:2019. The healthcheck got "connection refused" and marked a healthy Caddy (serving prod traffic with valid TLS) as unhealthy, making deploy.sh report a false "Container startup failed\!".

* test(focus-mode): update skip break e2e assertion

* test(focus-mode): update skip break expectation

* docs(supersync): align caddy admin healthcheck note to 127.0.0.1

Comment-only follow-up to the docker-compose healthcheck fix: the Caddyfile note still said localhost:2019; align it to the literal IP the probe now uses.

* test(focus-mode): restore mode selector locator

* refactor(tasks): make deadline auto-planning atomic

* test(focus-mode): restore mode selector locator

* fix(tasks): prevent duplicate subtask links

* fix(tasks): remove noisy warning chips

* fix(schedule): position over-budget badge correctly in day panel

* feat(tasks): auto add tasks with deadlines today to Today view

* refactor(tasks): make deadline auto-planning atomic

* Checkpoint deadline auto-plan review work

* fix(tasks): make deadline auto-planning atomic

* fix(tasks): clear orphaned remindAt when deadline auto-plan clears dueWithTime

Overdue tasks moved to Today via deadline auto-planning had their
dueWithTime cleared but kept remindAt, leaving a reminder anchored to a
time the task no longer has. Clear remindAt in the same atomic reducer
pass (one op), matching the planTasksForToday/dismissReminderOnly
convention. Document the auto-plan policy and deliberate decisions on
getDeadlineAutoPlanDecision.

* perf(tasks): O(1) Today-membership lookups in deadline auto-plan

Multi-review follow-up:
- getDeadlineAutoPlanDecision takes ReadonlySet instead of array; the
  defensive effect now passes its accumulating Set directly and hoists a
  Set for the due-task filters, removing the O(N*M) array-includes scan
  on the date-rollover path.
- Drop redundant isTodayWithOffset import; reuse the local
  getDateStrWithOffset helper (identical under the positive-finite
  timestamp guard).
- Un-export isTaskDueTodayBySchedule (file-internal only).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 21:27:40 +02:00
agncr
472e98471c
feat(schedule): add Shift/Ctrl+Arrow minute stepping to time inputs (#7634)
* feat(schedule): add Shift/Ctrl+Arrow minute stepping to time inputs

Shift+Arrow steps by 5 min, Ctrl/Cmd+Arrow by 15 min — hardcoded to match
calendar app conventions and keep the feature self-contained.

* fix(schedule): prevent hour-segment jumping on modifier+Arrow steps

Always apply minute-based stepping via stepTimeString() regardless of
which segment is focused, by blocking the native browser step with
preventDefault().

* fix(schedule): wire TimeStepDirective into dialogs and address review feedback

- Apply spTimeStep to time inputs in dialog-deadline and dialog-schedule-task
- Import TimeStepDirective in both dialog components
- Guard disabled/readOnly inputs to avoid mutating non-interactive fields
- Add readOnly/disabled early-return to directive
- Expand directive spec: add ngModel integration test, preventDefault assertions
  for all modifier paths, ev.repeat case; remove misleading duplicate hour-segment test
- Revert unrelated t.const.ts key reordering and en.json wording change
- Document HH:MM-only precondition in stepTimeString

* test(time-step): rename misleading repeat-key test

Name claimed the directive ignores ev.repeat keydowns, but it has
no repeat guard and the assertion verifies a held key still steps.
Rename to match the actual asserted behavior.

---------

Co-authored-by: agncr <agncr@users.noreply.github.com>
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
2026-05-18 21:21:53 +02:00
Johannes Millan
125133698f test(focus-mode): restore mode selector locator 2026-05-18 18:06:01 +02:00
Johannes Millan
5f97f56487 docs(supersync): align caddy admin healthcheck note to 127.0.0.1
Comment-only follow-up to the docker-compose healthcheck fix: the Caddyfile note still said localhost:2019; align it to the literal IP the probe now uses.
2026-05-18 16:06:45 +02:00
Johannes Millan
c4f9364e7b test(focus-mode): update skip break expectation 2026-05-18 15:39:50 +02:00
Johannes Millan
87596f4a47 test(focus-mode): update skip break e2e assertion 2026-05-18 15:35:59 +02:00
Johannes Millan
7da451d2a3 fix(supersync): use 127.0.0.1 in caddy healthcheck to avoid ::1 refusal
busybox wget in caddy:2.11-alpine resolves `localhost` to ::1 first, but Caddy binds its admin API to IPv4 127.0.0.1:2019. The healthcheck got "connection refused" and marked a healthy Caddy (serving prod traffic with valid TLS) as unhealthy, making deploy.sh report a false "Container startup failed\!".
2026-05-18 15:10:28 +02:00
Johannes Millan
fd11c2f85a Merge branch 'feat/debug-why-our-super-sync-production-001683'
* feat/debug-why-our-super-sync-production-001683:
  perf(sync): use indexed findFirst for op-download minSeq
2026-05-18 14:21:15 +02:00
Johannes Millan
410dac6785 perf(sync): use indexed findFirst for op-download minSeq
Prisma operation.aggregate({ _min: { serverSeq } }) compiles to MIN()
over a `SELECT ... OFFSET 0` subquery. The OFFSET is a Postgres planner
optimization fence, so the (user_id, server_seq) index could not serve a
first-row seek and the query degraded to a per-user O(N) scan. Under a
client reconnect stampede this ran minutes inside the 60s interactive
download transaction, blowing the tx timeout (500s) and exhausting the
connection pool (cascading upload+download failures).

Replace it with findFirst ordered by serverSeq asc, which compiles to
ORDER BY server_seq ASC LIMIT 1 — a guaranteed index seek on the existing
unique (user_id, server_seq) index. Behaviour-preserving: same minSeq
value and same null-when-empty semantics.

Update both consuming specs (operation-download.service, gap-detection)
to the two-findFirst-call shape and add a regression test asserting the
indexed query is used and the aggregate path is not reintroduced.
2026-05-18 14:21:02 +02:00
Johannes Millan
4689dfeb68 Merge branch 'feat/issue-7642-bcfb0d'
* feat/issue-7642-bcfb0d:
  fix(focus-mode): allow active timers to switch to flowtime
2026-05-18 14:08:32 +02:00
Johannes Millan
fdaee5ee10 Merge branch 'feat/issue-7416-c0e0bf'
* feat/issue-7416-c0e0bf:
  test(tasks): expand completion replay matrix
  test(tasks): add scheduled completion round trips
  fix(tasks): cover scheduled completion edge cases
  fix(tasks): preserve schedule when completing tasks
2026-05-18 14:08:21 +02:00
Johannes Millan
838db69e01 test(tasks): expand completion replay matrix 2026-05-18 13:55:53 +02:00
Johannes Millan
ec5ca89d75 fix(focus-mode): allow active timers to switch to flowtime 2026-05-18 13:49:17 +02:00
Johannes Millan
782698765d test(tasks): add scheduled completion round trips 2026-05-18 13:43:36 +02:00
Johannes Millan
74ce49428f fix(sync): support separate Nextcloud login name
Refs #7617
2026-05-18 13:43:23 +02:00
Johannes Millan
7675be0982 fix(config): validate start of next day boundary 2026-05-18 13:43:22 +02:00
Johannes Millan
eb381c187f fix(plugins): allow iframe-only plugin installs 2026-05-18 13:43:22 +02:00
dependabot[bot]
89ba858ce0
chore(deps)(deps): bump the github-actions-minor group with 4 updates (#7652)
Bumps the github-actions-minor group with 4 updates: [step-security/harden-runner](https://github.com/step-security/harden-runner), [browser-actions/setup-chrome](https://github.com/browser-actions/setup-chrome), [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) and [github/codeql-action](https://github.com/github/codeql-action).


Updates `step-security/harden-runner` from 2.19.1 to 2.19.3
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](a5ad31d6a1...ab7a9404c0)

Updates `browser-actions/setup-chrome` from 2.1.1 to 2.1.2
- [Release notes](https://github.com/browser-actions/setup-chrome/releases)
- [Changelog](https://github.com/browser-actions/setup-chrome/blob/master/CHANGELOG.md)
- [Commits](4f8e94349a...2e1d749697)

Updates `anthropics/claude-code-action` from 1.0.111 to 1.0.123
- [Release notes](https://github.com/anthropics/claude-code-action/releases)
- [Commits](fefa07e9c6...51ea8ea73a)

Updates `github/codeql-action` from 4.35.3 to 4.35.5
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e46ed2cbd0...9e0d7b8d25)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.19.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: browser-actions/setup-chrome
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: anthropics/claude-code-action
  dependency-version: 1.0.123
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
- dependency-name: github/codeql-action
  dependency-version: 4.35.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 13:41:41 +02:00
dependabot[bot]
87bf5f3d4c
chore(deps)(deps): bump cloudflare/wrangler-action from 3.15.0 to 4.0.0 (#7653)
Bumps [cloudflare/wrangler-action](https://github.com/cloudflare/wrangler-action) from 3.15.0 to 4.0.0.
- [Release notes](https://github.com/cloudflare/wrangler-action/releases)
- [Changelog](https://github.com/cloudflare/wrangler-action/blob/main/CHANGELOG.md)
- [Commits](9acf94ace1...ebbaa15849)

---
updated-dependencies:
- dependency-name: cloudflare/wrangler-action
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 13:41:31 +02:00
Johannes Millan
163a5aa2db fix(tasks): cover scheduled completion edge cases 2026-05-18 13:16:31 +02:00
Johannes Millan
7b638522b4 fix(tasks): preserve schedule when completing tasks 2026-05-18 12:50:37 +02:00
johannesjo
95be8028e8 Merge branch 'task/do-we-still-need-the-repair-system-81bbaa' 2026-05-17 00:06:17 +02:00
johannesjo
e3b60dd55d refactor(core): remove unused legacy DataRepairService
The op-log validation system (src/app/op-log/validation/) handles all
data repair at sync checkpoints. The legacy DataRepairService had no
callers anywhere in the app.
2026-05-17 00:02:19 +02:00
johannesjo
fb7da650be Merge branch 'spike/issue-7632-option-b' 2026-05-16 23:53:45 +02:00
johannesjo
333259f097 test(op-log): log validation rejection inside recoverFromLegacyData
The outer attemptRecovery catch already logs the thrown Error, but the
console timeline doesn't make it obvious that the failure was a typia
validation rejection (vs. an IDB error or anything else). Log a
distinguishable line with the typia error count and any cross-model
error before throwing so support can grep for it.
2026-05-16 23:49:00 +02:00
johannesjo
805c6f56ef fix(op-log): stop hydration popping native dialogs (#7631)
The startup hydration path called validateAndRepair(), which opens a
native confirm()/alert() dialog when typia flags state. On Windows this
steals renderer focus and leaves shortcuts/clicks dead until the window
is minimized and restored — exactly the symptom in #7631.

Switch hydration to binary validateState() (no dialogs) and treat
failures as non-fatal: dispatch the data so the UI is live, but skip
the snapshot save at Checkpoint C so a corrupted state is not cached
for next boot. Also gate two destructive paths with the same validator:
refuse to import legacy data that fails validation, and refuse to
clear the migration backup unless the migrated snapshot validates.

Drop the dead _repairMutex field and its guards.
2026-05-16 23:49:00 +02:00