Skip to content

fix(capture): offer Undo in a focused popout and put the caret back on Undo - #502

Merged
chhoumann merged 5 commits into
masterfrom
fix/capture-undo-popout
Oct 15, 2026
Merged

chhoumann merged 5 commits into
masterfrom
fix/capture-undo-popout

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Fixes #498
Fixes #499

Why

This fixes two capture Undo defects. With a popout focused, a capture saved, but its notice read "[object DocumentFragment]" and had no Undo. Obsidian shows the notice in the popout, but only takes a fragment built in the main window. After Undo of a capture that widened a table column, the caret sat before the table, so the next capture broke the table. Obsidian pads every row as the column widens, so Undo's edit spans the caret, and CodeMirror moves the caret to the edit's start.

What changed

  • noticeWithUndo builds its message with createFragment() in place of activeDocument. It also stops checking on a notice once the popout that shows it closes.
  • A capture keeps the caret it found in its editor. Undo puts that caret back after its edit, in that editor only.
  • New journeys: J-TS-21 (Undo in a focused popout), Undo of a capture into an off-screen table cell moves the caret before the table #499 with the table in view and scrolled out of view, and a stale table cell in a focused popout.

Scope

Tradeoffs

  • A fragment built for the window that shows the notice is what master did, and it is the bug. Obsidian reads the message with the main window's helpers, which take only a main window fragment.
  • Undo puts the caret back after its edit, because the edit moves a caret set before it to the table's start.
  • Undo could also prefer the tab the capture went into over the first tab. That needs more code and only helps two tabs of one note with a widened column, so it is left out.

Blast Radius

Every capture Undo and every Undo notice take this path, in the main window too. There the notice is unchanged, and Undo now also puts the caret back. Size: 102,969 gzip bytes, +53 over master, 31 under the budget.

Verification

  • The four new journeys fail on master and pass here on Obsidian 1.14.4. All 18 table journeys and J-TS-15 to J-TS-21 pass.
  • npm run test:e2e on a fresh vault, Obsidian 1.14.4: 238 of 238 pass.
  • Node 22.23.2: lint, format:check, typecheck, build, test (2,767), test:tooling (35) and docs:build pass. The table and capture journey files (36 tests) also pass on Obsidian 1.13.7.

Mutations, each run against the unit files for capture and the notice, and the target journeys:

Mutant Unit tests Journeys, Obsidian 1.14.4
Build the fragment from activeDocument (master) Main window fragment fails J-TS-21, stale popout cell fail
Build it with the popout's own createFragment Pass J-TS-21, stale popout cell fail
Check the notice only while its button is connected Closed popout fails Pass
No caret restore (master) Selection Undo fails Both #499 fail
Restore the caret before Undo's edit Pass Both #499 fail
Take the caret after the capture wrote Selection Undo fails Both #499 and J-TS-21 fail
Restore the caret in whichever editor reverts Other tab's caret fails Second pane fails
Skip ending a detached cell in a popout (V-494's mutant) Pass Stale popout cell fails

Docs at release

None. The docs do not mention capture Undo or popouts.

Before, master with a popout focused:

master, popout notice

After:

fix, popout notice

Before, master after Undo and the next capture. The capture went before the header:

master, next capture after Undo

After:

fix, next capture after Undo


Note

Medium Risk
Changes affect every capture Undo path and undo notices globally; behavior is well-covered by new unit and e2e tests but touches editor caret restoration and cross-window DOM.

Overview
Fixes capture Undo when a note is in a focused popout and when table column padding moves the caret after Undo.

Undo notices now build their DOM with Obsidian’s main-window createFragment() instead of the focused window’s document, so popout notices show readable text and a working Undo button instead of [object DocumentFragment]. Notice cleanup also stops polling once the popout window closes.

Capture Undo snapshots the editor’s selections at capture time and restores them after reverting the text, so a widened-table undo that spans the caret no longer leaves the cursor before the table and breaks the next capture into the cell.

Tests cover popout-focused capture/undo, caret/selection restore (including multi-tab and table scenarios), and new e2e helpers for focusing and interacting with note popout windows.

Reviewed by Cursor Bugbot for commit a504835. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix capture Undo to restore editor caret and work in focused popouts

  • Capture Undo now records the editor's selection list before the edit and restores it after reverting, so the caret returns to its prior position. See CaptureService.Snapshot and the revertIn helper in CaptureService.ts
  • Undo notices are now built with a main-window DocumentFragment instead of the active document, so Obsidian's Notice accepts them while a popout is focused. The notice cleanup poll also stops when its popout document closes, preventing a leaked timer
  • Adds extensive test coverage: caret restoration on Undo, Undo after the destination tab closes, table-cell capture in scrolled views, and a full E2E journey for capture plus Undo with a focused popout
  • Risk: Behavioral Change: Undo notices always render via the main window's fragment factory; check noticeWithUndo in undoNotice.ts if notice content ever depended on the active popout document

Macroscope summarized a504835.

… a column

J-TS-21 opens a draft in a popout, focuses that window and captures.
The capture saves, but on master the popout's notice reads
"[object DocumentFragment]" and has no Undo button.

Two journeys type into a table cell of a two-row table, with the table
in view and with it scrolled out of view, capture, Undo and capture
again. The capture widens the column, so Obsidian pads the row below
too. On master the Undo leaves the caret before the table header, and
the second capture lands there and breaks the table.

A third journey clicks into a cell of a note in a focused popout, lets
the cell go stale in Source mode, changes another row on disk, captures
and clicks Undo in the popout. It covers popout cells, which no journey
did before. On master the capture keeps the table, and the journey
stops at the missing Undo button.
With a popout focused, Obsidian shows a Notice in that popout, but
reads its message with the main window's helpers. They take a
DocumentFragment only from the main window, and show any other message
as its String(). The notice was built from activeDocument, the popout's
document, so it read "[object DocumentFragment]" and had no Undo.

createFragment builds in the main window, and Obsidian moves the
fragment into the popout, where Undo works.

Fixes #498
A capture into a table cell can widen its column, and Obsidian then
pads every row. Undo puts back the stretch the capture changed, which
then runs from the table's first changed row past the cell, so it holds
the caret, and CodeMirror moves the caret to the stretch's start. The
same edit makes Obsidian redraw the table and end the cell's editor.
The next capture then went before the table header and broke the
table.

A capture now keeps the selections of the editor it went into, and
Undo restores them after its edit, in that editor while it still edits
the note. The note is still put back only while it is exactly as the
capture left it.

Fixes #499
…ed popout's notice

Undo restored the selections a capture found into whichever editor it
reverted in. With the capturing tab closed and another tab of the note
open, that moved the other tab's caret. The snapshot now keeps the
caret with the editor it came from, and Undo restores it only there.

A popout that closes while its Undo notice shows keeps the notice in
its document, so the button stayed connected and the notice was checked
every second for good. The check now also ends once the button's
document has no window.

Unit tests cover both, and that the notice is built in the main window.
The table journey scrolled out of view now scrolls until the table
leaves the viewport, as CodeMirror can scroll back to the cell first.
The new e2e helpers say what they do in their names.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying podnotes with  Cloudflare Pages  Cloudflare Pages

Latest commit: a504835
Status: ✅  Deploy successful!
Preview URL: https://ee1f6e93.podnotes.pages.dev
Branch Preview URL: https://fix-capture-undo-popout.podnotes.pages.dev

View logs

@chhoumann
chhoumann merged commit 18ea5bf into master Oct 15, 2026
21 checks passed
@chhoumann
chhoumann deleted the fix/capture-undo-popout branch October 15, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant