Apple now requires Xcode 26 / iOS 26 SDK for App Store uploads
(mandatory since 2026-04-28). Switch the iOS release workflow to
the macos-26 runner and explicitly select Xcode 26.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat/ensureoverlaysclosed-1-backdrop-s-0-1f8487:
fix(tasks): track focus on inner elements without subtask bubbling
fix(shortcuts): keep shortcuts working while a tooltip is open
The 'focus' event does not bubble, so clicking a button inside a task
(title editor, hover controls, etc.) left focusedTaskId stale and
subsequent shortcuts targeted the wrong task. Switching to 'focusin'
fixes that, but its bubbling means a nested subtask's focusin would
also reach every ancestor task host and overwrite focusedTaskId with
the parent's id. Add an innermost-task guard so only the closest
<task> ancestor of the event target claims focus. Pair 'focusout' with
a relatedTarget check to avoid clearing focusedTaskId when focus moves
between elements within the same task.
A lingering Material tooltip pane (mat-mdc-tooltip-panel) leaves a CDK
overlay mounted, which made _hasOpenCdkOverlay() report a blocking
overlay and silently swallow every keyboard shortcut until the tooltip
faded out. Tooltips are non-interactive — exclude them from the check.
The wiki is now ready for general use. I have added some notes regarding
the use of DeepWiki and how it complements the official one. The
`.devin` directory contains a file that should help steer the auto-doc
process. My two primary aims are:
1. avoid polluting the DeepWiki resource with the existing docs in case
of error propagation and LLM context poisoning
2. Generate a differential of the observed code and the observed content
in docs/wiki to aid in future doc updates.
The @defer block around <focus-mode-overlay> had no explicit trigger
and so defaulted to `on idle`. When isShowFocusOverlay() became true,
the @else branch hid the entire app container while requestIdleCallback
waited for browser idle to fetch the chunk. On a busy page (sync
flushes, store updates) this could take several seconds, leaving the
viewport blank and causing intermittent e2e timeouts on the focus-mode
specs.
Switch the trigger to `on immediate` so the chunk loads as soon as the
overlay is needed. The chunk stays in its own bundle.
The fallback branch in _convertToLWWUpdatesIfNeeded rewrote actionType to
'[TASK] LWW Update' but left the payload in its NgRx UPDATE shape. That
payload has no top-level id, so lwwUpdateMetaReducer bails at the
missing-id guard before reaching the recreate branch — meaning the
RECREATE_FALLBACK warn cannot fire from this path. The producer's error
log and the consumer's "Likely cause" message both implied otherwise.
- Drop the dead actionType rewrite; return the remote op unchanged. The
resulting [Task Shared] updateTask action hits handleUpdateTask, which
early-exits on missing entity (task-shared-crud.reducer.ts:588) — same
no-op outcome as before, no cross-entity bookkeeping fires.
- Downgrade OpLog.error -> warn with text reflecting actual outcome
(entity stays deleted, no consumer-side recovery from this path).
- Fix the consumer warn's "Likely cause" sentence: drop the
_convertToLWWUpdatesIfNeeded reference; the actual cause is
partial-baseEntity from a minimal DELETE op payload on the happy path.
- Update three specs in conflict-resolution.service.spec.ts that asserted
the old actionType rewrite.
Refs: #7447 (closed)
When editing a recurring task to move startDate earlier than the existing
lastTaskCreationDay, the live instance was rescheduled to the day after
the OLD startDate (getNextRepeatOccurrence advanced past the stale anchor)
instead of landing on the NEW startDate. Re-anchor via getFirstRepeatOccurrence
in that case. Skipped for repeatFromCompletionDate configs where startDate
is decoupled from scheduling.
Follow-up to 2c073067 that closes residual gaps in the LWW Update
recreate path identified in code review.
Gap 1: TASK projectId left undefined.
DEFAULT_TASK Omits projectId (it varies per task), but TaskCopy declares
it required. A partial LWW Update payload that does not carry projectId
left the recreated task with projectId undefined, still failing Typia.
Backfill with INBOX_PROJECT.id, but guard with the same fall-through
pattern as normalizeRestoredTask (task-shared-lifecycle.reducer.ts:110)
so an absent INBOX_PROJECT falls back to the first available project.
Add a matching autoFixTypiaErrors rule for any state already on disk.
Gap 2: PROJECT and TAG hit the same recreate branch unprotected.
The recreate logic is generic across entity types but the DEFAULT_*
merge was TASK-only. Replace the hardcoded TASK branch with a single
RECREATE_FALLBACK registry pairing per-type `defaults` with the
`requiredKeys` that drive the diagnostic warn — pairing makes the
lockstep invariant structural rather than tribal.
Gap 3: null and undefined slip past the partial-payload check.
Producers that emit `null` (rather than omitting a field) bypassed the
`=== undefined` warn AND had `null` overwrite the default in the spread.
Treat both as "missing": warn check uses `== null`, and null/undefined
values are stripped from the action payload before merging with
defaults so they cannot clobber a backfill.
Gap 4: warn copy implied a single root cause.
The producer emitting partial payloads is one source; another is a
local DELETE op carrying only {id} as its base entity. Reword the warn
to name both, and add `attachments` to the TASK enumeration (it was
already in DEFAULT_TASK and the auto-fix rule but missing from the
warn list).
Integration test (#7330): drive the worst-case producer-shape payload
through the meta-reducer and assert the resulting TaskState passes the
real `appDataValidators.task` Typia validator. End-to-end proof that
the user's "Repair attempted but failed" dead-end cannot fire from this
upstream path.
Verified: 85 meta-reducer specs and 12 auto-fix specs pass in
Europe/Berlin and America/Los_Angeles. conflict-resolution and
data-repair specs unaffected.
The page title was showing the last work-context name (e.g. "Today") and
the work-context three-dot menu on `/search`, `/scheduled-list`, `/donate`,
and `/config`, even though those routes are not work contexts. Settings
also rendered a redundant "Global Settings" H1 in the body.
Closes#7426
Locks down the cold-start-after-miss path: configures a monthly
"first of month" recurring task on Jun 1, jumps to Jul 3 — skipping
Jul 1 entirely — and asserts the missed Jul 1 instance is materialised
on reload.
Uses setSystemTime rather than setFixedTime so the 1s debounceTime in
TaskDueEffects.createRepeatableTasksAndAddDueToday$ keeps ticking on
the freshly-reloaded page.
The dialog uses position:fixed which bypasses the body's safe-area
padding, so on Android Capacitor (e.g. Pixel 9 Pro) the formatting
toolbar and first textarea line render under the status bar. Apply
safe-area padding directly on the host for body.isNativeMobile and
adjust the keyboard-height calc to keep its math correct.
Closes#7442
Commit 5772b3416c removed user-entered task data from the main-task
log call to address GHSA-jpwx-59x4-rprg, but the parallel subtask log
in PluginBridge.addTask was missed and still included the full
taskData (title, notes) in the log payload. Logs are exportable via
the bug-report flow, so this leaks the same content the GHSA fix was
meant to redact.
Match the redaction pattern of the main-task log: include only the
internal taskId.
The Brain Dump plugin (and any plugin using PluginAPI.addTask with a
parentId) dispatches addSubTask, which is not in the ofType filter of
ShortSyntaxEffects. Subtask titles like "subtask1 15m" were stored
verbatim and the default sub-task estimate was applied instead.
Mirror MarkdownPasteService._parseTimeProps in PluginBridge.addTask:
when shortSyntax.isEnableDue is on, run parseTimeSpentChanges on the
subtask title before createNewTaskWithDefaults. Tags/projects are still
not parsed because subtasks inherit them from the parent.
When an LWW Update arrives for a task that does not exist locally,
lwwUpdateMetaReducer recreates the entity by calling adapter.addOne with
the action payload. Some upstream producers (notably the
_convertToLWWUpdatesIfNeeded fallback in conflict-resolution.service)
emit partial payloads that only carry the changed fields. The recreated
task then has required fields (`title`, `timeSpentOnDay`, `tagIds`,
`subTaskIds`) undefined, which fails Typia validation.
dataRepair had no rule for those undefined fields, so the user got stuck
on the "Repair attempted but failed to fully fix data issues" dialog with
no recovery path (issue #7330).
This patch:
- Merges DEFAULT_TASK before addOne in the recreate branch so a TASK is
always schema-valid, regardless of which producer fed the payload.
- Logs a warn when a TASK LWW Update arrives partial, so the upstream
producer can be identified from logs in any future report.
- Adds defense-in-depth rules to autoFixTypiaErrors for
task.entities[*].title, .timeSpentOnDay, .tagIds, .subTaskIds and
.attachments so dataRepair can recover any state that was already
corrupted on disk before this fix shipped.
Closes#7330
Stops the GlobalErrorHandler from showing the "please report on GitHub"
dialog for IndexedDB open failures. The hydrator already shows a tailored
recovery dialog; runtime callers (TimeTracking, Worklog, Archive, Sync)
now fail silently with a console log entry instead of asking users to
file what is usually a known transient platform issue (e.g. WebKit bug
273827 on iPadOS).
The wrapper's message and the handled-error marker now include the
original error's name and message, and both throw sites log the wrapper
itself, so the next user report contains the underlying cause needed to
distinguish Chromium LevelDB locks, WebKit "Connection to Indexed
Database server lost", quota errors, etc.
Closes#7415
Paired migration:
- stylelint 16.26 -> 17.9
- stylelint-config-recommended-scss 14.1 -> 17.0
- @csstools/stylelint-formatter-github 1.0 -> 2.0 (requires stylelint 17)
Fix the 26 violations from new deprecation rules:
- 'word-wrap' replaced with 'overflow-wrap' (autofix; word-wrap is a
legacy alias in CSS3, only matters that stylelint now flags it)
- 'word-break: break-word' (deprecated keyword) replaced with
'overflow-wrap: anywhere' (the spec-defined equivalent), with stale
duplicate 'overflow-wrap' declarations consolidated
- cross-env 7→10 (CLI-compatible, Node 20+ engine — already met)
- eslint 9→10 + @eslint/js 9→10 (all active plugins support v10;
fix local rule require-hydration-guard to use context.sourceCode
instead of removed context.getSourceCode())
- jasmine-core 5→6 (forbidDuplicateNames default flipped; no duplicates
in our suite — verified by running the full test:once)
- typia 11→12 (public API surface unchanged; validation specs pass)
Lint, build and the typia/auto-fix specs verified green. The 5 pre-existing
failures in immediate-upload.service.spec.ts are unrelated — also fail on
master with the same package.json that has been shipping.
Deferred from this round: stylelint 16→17 (paired migration with
@csstools/stylelint-formatter-github 1→2, separate work),
electron-dl 3→4 (forces full electron-main CJS→ESM migration),
marked 17→18 (ngx-markdown@21.2.0 peer-locks marked at ^17).
Patch/minor updates across tooling and runtime:
electron 41.2→41.4, electron-builder 26.7→26.8, prettier 3.7→3.8,
playwright 1.57→1.59, typescript-eslint 8.52→8.59, karma 6.4.2→6.4.4,
core-js 3.47→3.49, nanoid 5.1.6→5.1.11, dotenv 17.3→17.4, glob 13.0→13.0.6,
fs-extra 11.3→11.3.4, tslib 2.7→2.8, baseline-browser-mapping 2.9→2.10,
plus type defs and native binaries (@lmdb/*, @rollup/*).
Build, lint and a sample unit test all pass.
Replace plain title text with <tag> in chip rows and autocomplete
options so the recurring task config (and the two other consumers of
chip-list-input) show colored dots for both selected chips and dropdown
suggestions, matching the rest of the app.
Right-anchor the inline-input when used inside the planner time cell so
the expanding edit field grows leftward instead of bleeding past the
planner-day overflow:hidden boundary.
Reverts d74a621d68. Hiding the play button until a task is being
tracked made starting the timer a 2-step gesture (swipe + menu) on
mobile. Users who don't want the button can already disable it via
app features.
* 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.
A long-suspended Android client could produce dozens of LWW conflicts
on TAG:TODAY when it resumed mid-day. Trace from the incident log:
+0ms androidInterface.onResume$ emits (I_RESUME_APP)
+106ms visibilitychange → todayDateStr$ → DAY_CHANGE
+112ms setTodayString action dispatched
+117ms repairTodayTagConsistency$ fires with stale local taskIds
+124ms ...creating a [Tag] Update Tag op in the local op log
+241ms SyncWrapperService.sync() actually runs and downloads
~600 remote ops, 22 of them TAG:TODAY updates from other
clients during the day → 22-way LWW conflict
The repair effect is gated on `HydrationStateService.isInSyncWindow`,
but on resume that window opened too late: the trigger pipeline has
`debounceTime(100)` plus exhaustMap latency, so the synchronous
visibility-change → repair cascade finishes before sync() runs.
Add a third phase (`_isSyncWindowOpen`) to `isInSyncWindow`. Open it
via `openSyncWindow()` from three places that all fire ahead of the
cascade:
1. The tap right after `_immediateSyncTrigger$` in
`SyncTriggerService.getSyncTrigger$()`, before debounceTime.
2. Direct subscriptions to `androidInterface.onResume$` and
`ipcResume$` in the SyncTriggerService constructor — covers
the cold-start case where (1)'s switchMap chain is not yet
subscribed.
3. `SyncWrapperService.sync()` itself, before any async work.
`closeSyncWindow()` runs in `SyncWrapperService.sync()`'s finally
block. The 2s failsafe inside `openSyncWindow()` handles triggers
that get debounced/throttled out before reaching `sync()`. When
`sync()` opens the window itself, it passes failsafeMs=0 to opt out
of the timer — its own `finally` is the authoritative close, and a
slow sync (provider I/O > 2s) would otherwise expire the timer
mid-sync and leave a stale-state gap.
Also adds a verbose log in `skipDuringSyncWindow` so silent drops
of legitimate emissions during the wider window are observable in
the field.
Tests: 29 hydration-state, 100 sync-wrapper, 14 tag.effects (incl. an
integration test that drives the real HydrationStateService through
the gate end-to-end), 15 sync-trigger, 11 skip-during-sync-window —
all green. Verified against multi-agent code review (6 Claude
reviewers + Codex CLI).
Adds the ability to schedule monthly recurring tasks anchored to a
specific ordinal weekday — "first Thursday", "last Monday", "second
Monday" — alongside the existing same-numeric-day-of-month recurrence.
Model: two optional fields on TaskRepeatCfg, monthlyWeekOfMonth (1..4
or -1 for last) and monthlyWeekday (0=Sun..6=Sat). Anchor presence is
the single discriminator — both fields set and in range means "Nth
weekday", anything else falls back to legacy day-of-month behavior.
Calc: a shared findMonthlyNthWeekdayOccurrence helper walks months
forward/backward computing each month's Nth-weekday candidate; the
three recurrence calc utils (next/first/newest-possible-due-date) pass
their own accept predicate. hasNthWeekdayAnchor is a narrowing type
guard that also validates ranges, so a malformed sync payload falls
back to day-of-month rather than producing wrong dates from arithmetic
on garbage inputs. data-repair strips the legacy monthlyMode
discriminator field from any cfg persisted by an in-development build.
UI: a "(Day of month)" sentinel option in the week-of-month select acts
as the toggle; the weekday select hides when no ordinal is picked. One
new quick-setting "Every month on the {ordinal} {weekday}" infers the
anchor from the start date. The save flow normalizes the form's null
sentinel back to undefined so existing cfgs don't dispatch spurious
change diffs.
The duration formly component clears empty input to undefined, so the
type should reflect that. Functionally a no-op since the only consumer
coalesces falsy values to 0.
The "Show a notification X before the event" field could not be cleared:
the form's onInit hook reset any falsy-but-not-null value back to 2h on
reopen. The duration formly component clears empty input to undefined
(not null), so the \!== null guard never matched and the user's blank
value was always overwritten.
Removing the hook lets cleared values persist. New providers still get
the 2h default from DEFAULT_CALENDAR_CFG.
Closes#7410
Both end-of-break buttons dispatched the same completeBreak action and
auto-started the next cycle. Wire the secondary button to
exitBreakToPlanning, relabel it "Complete focus session", and let the
button row wrap on narrow viewports while hiding "Reset cycles" below
600px.