Skip to content

Stabilize task persistence, OAuth, recurrence and calendar behavior for next release - #2323

Merged
callumalpass merged 30 commits into
mainfrom
fix/next-release-reliability
Sep 13, 2026
Merged

callumalpass merged 30 commits into
mainfrom
fix/next-release-reliability

Conversation

@callumalpass

@callumalpass callumalpass commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

Integrated candidate for the agreed next-release reliability shortlist. No version bump, tag, release, or issue closeout.

Integrated existing work

Existing PR commits are retained through merge commits; contributor credits are consolidated in the release notes.

Follow-up fixes from integration review

Verification

  • Full Jest: 712 suites / 4,230 tests passed; 70 existing skipped suites, 1,011 skipped tests, 4 todo. No failures.
  • Full lint (TypeScript, review types, CSS, architecture) passed.
  • Typecheck and npm run build:test passed.
  • Documentation source checks passed after initializing this worktree's pinned spec submodule.
  • Recurrence + real-ical override tests: 19 passed in each of UTC, America/New_York and Australia/Melbourne.
  • Native disposable test-vault smoke: actual Edit-task tag save retains a long fallback title; Reading mode keeps the card with both visible and hidden properties, with zero measured scroll drift and no direct-sizer card insertion; actual custom recurrence editor retains seconds and normalizes both Z/no-Z input; instance completion retains the anchor time; ICS moved/cancelled overrides behave correctly.
  • Native OAuth smoke used a separate service with synthetic credentials and a real localhost callback server, with token exchange/storage stubbed. It completed despite a pending browser-launch promise, used matching redirect URIs and rejected overlapping attempts. No real Google/Microsoft login claimed.
  • Test plugin files restored byte-for-byte to their original versions, then reloaded; no captured errors. Canonical checkout and existing worktrees were not modified by candidate integration.

Dispositions / remaining gates

Refs #2291, #2294, #2292, #2322, #2109, #2124, #2300, #2255, #2318, #2319, #2309, #2298, #2299, #2279, #2316, #2055, #2043, #2321, #2284, #2313, #2314.

ther12k and others added 30 commits June 21, 2026 17:28
…on + map filter (closes #2043)

The default-relationships Subtasks filter generated by
formatProjectEntryLinkExpression in defaultBasesFiles.ts emits:
  file.hasLink(this.file) && list(note.PROP).map(<link-normalizer>).asLink()).contains(this.file.asLink())

The currentFileContainsMatch regex captures the entire conjunction as the
property expression. normalizeFilterProperty then sees:
  file.hasLink(this.file) && list(note.projects).map(...).asLink())
which fails every check (no leading 'list(' since the string starts with
'file.hasLink', no trailing match for the core field set, etc.) and
returns null. Net effect: clicking the column '+' button on the default
Subtasks tab creates a task with no 'projects' field, which then fails the
view filter and disappears.

Fix: strip the &&-joined left side and the generated .map(...) wrapper
(balanced-paren walk, since .asLink() lives inside the .map argument) before
the existing list()/note-prefix recognition runs.

Tests:
  - 3 new regression tests in basesFilterDefaults.test.ts covering the
    generated core-field case, the user-defined-field case, and the
    missing-current-file-link fallback.
  - All 6/6 tests in basesFilterDefaults.test.ts pass.
  - All 13/13 tests across kanbanCreationDefaults, basesTaskCreation,
    KanbanView.manualOrderFastPath, and basesCreateFileForView pass.

Refs: #2043, #1657, #1902.
…tion cannot delete it

Reading mode is virtualised, and every render pass ends with
`sizerEl.setChildrenInPlace([pusherEl, ...shownSections])`, which deletes any direct
child of `.markdown-preview-sizer` that Obsidian did not put there. The task card was
injected between sections, so it was deleted on every pass and re-injected on the next
frame. During a scroll that cycle ran continuously and dragged the reader down the note
until it hit the bottom (#2255).

The scroll quiet period added in b29a29e defers re-injection but cannot stop this,
because the problem is where the re-injection lands rather than how often it happens.
Once the reader scrolls past the top of the note, Obsidian has detached
`.mod-header.mod-ui` too, so `getMetadataOrHeaderInsertionReference` falls through to
the preview pusher - whose next sibling is the first section that is *currently
rendered*, not the first section of the note. The card is therefore inserted into the
middle of the text being read, shoving the page down by its own height. Measured on two
notes, each correction was exactly the card's occupied height (579px and 690px), and the
corrections arrived every 218-262ms, which is the 200ms quiet period plus a frame.

`setChildrenInPlace` only manages *direct* children, so nesting the card one level deeper
leaves it untouched by the render pass. It is now placed inside the header, after the
properties block. Two supporting changes make that safe:

- The observer skips re-injection while the header is not rendered. Obsidian detaches
  the header with the card inside it when the reader scrolls past the top; that must be
  left alone rather than fought, and the card is restored when the header comes back.
- Injection anchors on the properties block rather than the end of the header. Obsidian
  builds the header in stages, so appending during an early pass put the card above the
  properties, which then rendered underneath and shoved it down - a visible jolt on
  every note open.

Nesting also corrects the height accounting noted in the issue: `measureSection` takes a
section's height as the gap between its own `offsetTop` and its next sibling's, so a
widget between sections is counted in neither. Inside the header it is counted in that
section.

Spacing is scoped to `.markdown-preview-view .mod-header` so Live Preview and canvas are
unchanged. The gap below the card uses `--p-spacing`, the note's own gap between blocks.
The `:has()` selector that trims the properties block's bottom margin needs Chromium
105+; where it does not apply the gap is simply larger, never broken.

Note that requiring the properties block means a task note rendering with no
`.metadata-container` at all would not receive a card.
`observeReadingModeWidgetMutations` is shared: TaskCardNoteDecorations and
RelationshipsDecorations both use it. Requiring the header anchor unconditionally
would have blocked the relationships widget from injecting whenever the header is
detached - which is precisely where that widget lives, since it defaults to the
bottom of the note.

The guard is now `requireHeaderAnchor`, off by default, set only by the task card,
which is the widget that nests inside the header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBWuKe71PRSJaN2SM4xUFJ
A Google calendar enabled as `primary` is fetched under that alias, so its
events carry `primary` as their calendar id while the calendar list reports the
account's real id. Event cards already reconcile the two; three other lookups
compared ids directly and missed.

The calendar's own color was replaced by the default Google blue, its
per-calendar visibility toggle matched nothing and so never hid anything, and
mini calendar entries and event-linked notes fell back to the generic provider
name.

Resolve the alias through one shared helper, and register the account's own
calendar under both keys when building visibility toggles. Event and
subscription ids are unchanged, so notes already linked to alias-fetched events
still match.
# Conflicts:
#	docs/releases/unreleased.md
# Conflicts:
#	docs/releases/unreleased.md
# Conflicts:
#	docs/releases/unreleased.md
# Conflicts:
#	docs/releases/unreleased.md
@callumalpass
callumalpass merged commit 22a335a into main Sep 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants