Skip to content

fix(builders): open every choice builder with focus on Done - #1812

Merged
chhoumann merged 1 commit into
masterfrom
fix/builder-initial-focus
Sep 26, 2026
Merged

chhoumann merged 1 commit into
masterfrom
fix/builder-initial-focus

Conversation

@chhoumann

@chhoumann chhoumann commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

The choice builders opened with focus on different elements. The Macro builder focused its title rename button, which showed a focus ring around the title (visible in the #1809 screenshots). The Template and Capture builders focused the footer Done button. Now all three open with focus on Done.

Cause (confirmed in Obsidian 1.13.7)

Modal.prototype.open() in Obsidian's app.js runs this.onOpen() and then, when hasPhysicalKeyboard && this.hasInitialInputFocus, calls a helper that focuses the first visible element in modalEl that matches a focusable selector, in DOM order (it ignores autofocus). No QuickAdd code set focus.

  • MacroBuilder builds contentEl (title first) before open(), so the title's rename button was first.
  • ChoiceBuilder (Template/Capture) calls open() from its constructor, and the Svelte form mounts after that, so the footer's Done was the only focusable element at open().
  • The Multi "Edit folder" modal (MultiChoiceSettingsModal) focuses its Name input. I left it unchanged on purpose: it is a small Save/Cancel form without the autosave footer, and typing a name is what the user opens it for.

Fix

addAutosaveFooter now calls done.focus(), and both builder hosts (ChoiceBuilder, MacroBuilder) call it after open(), so it runs after Obsidian's autofocus. Two statements were reordered and one line was added. No new state or options.

Why Done:

  • It is the builder's primary action (mod-cta) and already the target in Template/Capture, so only the Macro builder changes.
  • Enter or Space on it only closes the builder, and closing autosaves, so an accidental keypress loses nothing. Focusing the title instead meant Enter opened a rename prompt, and focusing a settings field would put a focus ring and caret in the middle of the form.
  • Keyboard users can reach the settings with Shift+Tab from Done (verified below) or with Tab, and Escape still closes.

On devices without a physical keyboard, Obsidian skips its own autofocus. This change focuses Done unconditionally, which does not open a soft keyboard because Done is a button.

Before / after (real Obsidian 1.13.7, isolated e2e vault, made-up choices)

Macro builder: before, the focus ring is on the title. After, it is on Done.

Macro builder before/after: focus moves from the title rename button to Done

Template builder: unchanged (Done both times).

Template builder before/after: Done focused in both

Capture builder: unchanged (Done both times).

Capture builder before/after: Done focused in both

Verification

I opened each builder through the real UI (Settings → QuickAdd → Configure <choice>) with pnpm run obsidian:e2e -- eval, read document.activeElement 800 ms after the click, then closed it with a real Escape key (webContents.sendInputEvent). There was exactly one builder open at each reading, and none left after Escape. The instance was restarted before the run.

Modal document.activeElement on master on this branch
Template ("Daily recipe") button.mod-cta "Done" in .qa-builder-footer same
Capture ("Quick idea") button.mod-cta "Done" in .qa-builder-footer same
Macro ("Morning routine") button.choiceNameHeaderButton.qa-rename-title-button in h2.choiceNameHeader button.mod-cta "Done" in .qa-builder-footer
Multi "Edit folder" ("Kitchen") Name input same (intentionally)

Real keyboard input on this branch (sendInputEvent):

  • Macro builder: Shift+Tab from Done moves focus into the form (input), and Tab returns to Done.
  • Enter on the focused Done closes the Macro, Template, and Capture builders (open builders go from 1 to 0), and the settings modal underneath stays open.
  • dev:errors: "No errors captured."

Checks:

  • pnpm run build-with-lint: passes.
  • pnpm run test: 448 files passed, 5 skipped; 5811 tests passed, 24 skipped.
  • New src/gui/ChoiceBuilder/builderInitialFocus.test.ts opens the Macro, Template, and Capture builders and asserts that document.activeElement is the footer's Done. The Obsidian stub's open() does not autofocus, so the test wraps it to focus the first focusable element in modalEl, as Obsidian does. On master the Macro case fails. It also fails for the plausible wrong fix, where done.focus() is added but the call is left before open(), because the emulated autofocus then moves focus back to the title.

Notes

  • No release or migration impact. No generated artifacts are committed (main.js and styles.css are gitignored).

Note

Fix choice builders to focus Done button after modal open

  • Choice builders (Template, Capture, Macro) previously focused a control chosen during Modal.open(). The autosave footer is now added after the modal opens, so its Done button becomes the final focus target.
  • addAutosaveFooter calls focus on the Done button right after insertion, and constructors in choiceBuilder.ts and MacroBuilder.ts were reordered to open the modal before installing the footer.
  • Adds a Vitest suite in builderInitialFocus.test.ts that mocks the Dataview API, emulates Obsidian's modal autofocus, and asserts Done is the active element in all three builders.
  • Behavioral Change: keyboard focus now lands on Done instead of the first focusable control when these builders open.

Macroscope summarized 2d14325.

Summary by CodeRabbit

  • Bug Fixes
    • The Done button now receives initial focus when opening Macro, Template, and Capture choice builders, making it easier to proceed using the keyboard.
    • The footer retains its placement when the Macro builder reloads, keeping the Done button available after the content is rebuilt.

Obsidian's Modal.open() focuses the first focusable element. The Macro
builder builds its content before open(), so its title rename button got
focus and a focus ring; the Template/Capture forms mount after open(), so
their footer Done got it. addAutosaveFooter now focuses Done and both
builder hosts call it after open().

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df6a-959f-74bb-a96a-d523ad2e20e9
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T20:48:10.284372Z 2d14325 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 707f8366-f7bf-497a-b026-ed4ab9cee406

📥 Commits

Reviewing files that changed from the base of the PR and between 1b34572 and 2d14325.

📒 Files selected for processing (4)
  • src/gui/ChoiceBuilder/builderInitialFocus.test.ts
  • src/gui/ChoiceBuilder/choiceBuilder.ts
  • src/gui/ChoiceBuilder/components/autosaveFooter.ts
  • src/gui/MacroGUIs/MacroBuilder.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The autosave footer now focuses its Done button. ChoiceBuilder and MacroBuilder install the footer after opening their modals. A test checks the initial focus in Macro, Template, and Capture builders.

Changes

Builder initial focus

Layer / File(s) Summary
Focus the autosave Done button
src/gui/ChoiceBuilder/components/autosaveFooter.ts
The helper focuses Done. Its documentation describes the intended initial focus and says to call the helper after modal.open().
Install the footer after modal open
src/gui/ChoiceBuilder/choiceBuilder.ts, src/gui/MacroGUIs/MacroBuilder.ts, src/gui/ChoiceBuilder/builderInitialFocus.test.ts
Both builders open the modal before installing the footer. The test checks that Done is active in Macro, Template, and Capture builders.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 2d143

The builders are set up to focus Done when opened. No issue identified here prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: opening every choice builder with initial focus on the Done button.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 4 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit taps the Done button bright,
Then watches focus land just right.
Three builders pass the gentle test,
Their footer buttons lead the quest.
The rabbit hops off, pleased and light.

Comment @coderabbitai help to get the list of available commands.

@chhoumann
chhoumann merged commit eaa8523 into master Sep 26, 2026
15 checks passed
@chhoumann
chhoumann deleted the fix/builder-initial-focus branch September 26, 2026 20:52
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.

2 participants