Skip to content

fix: frontmatter-aware top insertion for capture and apply-template#1334

Merged
chhoumann merged 2 commits into
masterfrom
chhoumann/647-frontmatter-aware-append
Jun 13, 2026
Merged

fix: frontmatter-aware top insertion for capture and apply-template#1334
chhoumann merged 2 commits into
masterfrom
chhoumann/647-frontmatter-aware-append

Conversation

@chhoumann

@chhoumann chhoumann commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Summary

Capture's write to top and apply template to active note → top could insert content above a note's YAML frontmatter (corrupting it) and/or glue the inserted text onto the first line of the body. This routes both paths through one frontmatter-aware insertion primitive.

The bug (two manifestations, one root cause)

A note whose frontmatter the old code couldn't see — most commonly empty frontmatter (---\n---, e.g. after all properties are removed) — or a frontmatter-only note with no trailing newline (---\ntitle\n---) had the captured/template text written onto or above the ---, breaking the block.

  • Capture ([FEATURE REQUEST] Appending to existing file while considering the frontmatter #647): detected frontmatter via the metadata cache + a custom regex. Empty/cold-cache frontmatter was missed → capture prepended above the fence. Even when detected, a single-line capture was glued onto the first body line.
  • Apply template (top): insertBodyIntoNoteContent glued the body onto the closing fence for a frontmatter-only target with no trailing newline.

Fix

New pure helper src/utils/noteContentInsertion.ts (getBodyStartOffset / insertAtNoteBodyStart) built on Obsidian's own getFrontMatterInfo — content-based (a stale/cold metadata cache can no longer misplace text), with leading + trailing CRLF-aware separators so the text always lands on its own line and never touches the fence.

Both paths use it:

  • The 4 capture "top" sites (default top + the 3 create-if-not-found at top branches) — tight insertion.
  • TemplateInsertEngine.insertBodyIntoNoteContent's top branch passes body + "\n", preserving its deliberate block separation.

The two callers keep distinct spacing policies (capture = tight snippet; template = separated block) over one shared, fence-safe implementation. Removed the old cache+regex detector (getFrontmatterEndPosition, inferFrontmatterEndLineFromContent) and the now-dead pos === -1 branch.

Behavior change (Obsidian-consistent)

A --- fence not at offset 0 (e.g. preceded by a blank line) or closed with ... is now treated as no frontmatter — matching Obsidian's own parser — so the capture lands at the very top.

Testing / validation

  • New unit tests for the helper + capture/template frontmatter cases: empty FM, FM-only-no-newline (the fence-glue), CRLF body + blank-line absorption, leading-blank, ...-close, task, repeated stacking, block-separation. Updated one capture test that had codified the old glued output.
  • Full suite green (1954 tests); pnpm run build-with-lint clean.
  • Verified live in a dev vault (real Obsidian) for both paths — including the empty-frontmatter and frontmatter-only-no-trailing-newline cases that previously corrupted the note, and applyTemplateToActiveFile(..., { mode: "top" }).

Also fixes a latent fence-glue bug in the apply-template-to-active-note path (the feature shipped for #526).

Closes #647

Checklist

  • PR title follows Conventional Commits
  • Linked the related issue
  • Release impact: fix: (patch)

Summary by CodeRabbit

  • Bug Fixes

    • Improved capture and template insertion at note start to properly respect YAML frontmatter boundaries.
    • Fixed line-break handling and CRLF preservation when inserting content above existing note body.
    • Ensured insertions run as atomic read-modify-write updates to avoid partial writes during insertion.
    • Enhanced consistency in insertion behavior across different note configurations.
  • Tests

    • Added comprehensive tests covering frontmatter-edge cases, CRLF handling, and top-insertion scenarios.

Capture 'write to top' and the apply-template 'top' insert placed content
above a note's YAML frontmatter (breaking it) when the frontmatter was empty
or otherwise undetected, and glued the inserted text onto the first body line.

Route both through a shared, fence-safe getFrontMatterInfo-based helper
(insertAtNoteBodyStart): capture inserts tightly; apply-template passes body+\n
to keep its block separation. Detection is content-based (no stale metadata
cache) and CRLF-aware.

Closes #647
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5b03d203-6550-4a08-8f0c-797268e940c1

📥 Commits

Reviewing files that changed from the base of the PR and between 1e512ba and 55f95e2.

📒 Files selected for processing (3)
  • src/engine/TemplateInsertEngine.test.ts
  • src/engine/TemplateInsertEngine.ts
  • tests/obsidian-stub.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/engine/TemplateInsertEngine.test.ts

📝 Walkthrough

Walkthrough

This PR centralizes frontmatter-aware insertion via getBodyStartOffset and insertAtNoteBodyStart, updates consumers (TemplateInsertEngine, CaptureChoiceFormatter) to use them, switches Template insertion to app.vault.process, and adds tests covering frontmatter, newline, and CRLF edge cases.

Changes

Frontmatter-safe note insertion refactoring

Layer / File(s) Summary
Body-start insertion utilities and tests
src/utils/noteContentInsertion.ts, src/utils/noteContentInsertion.test.ts
getBodyStartOffset computes the body-start byte offset after YAML frontmatter; insertAtNoteBodyStart inserts text at that boundary with CRLF-aware leading/trailing newline rules, no-op for empty payloads, and stacking semantics. Tests cover frontmatter variants, edge cases, and repeated insertions.
TemplateInsertEngine refactoring to use insertAtNoteBodyStart
src/engine/TemplateInsertEngine.ts, src/engine/TemplateInsertEngine.test.ts, tests/obsidian-stub.ts
insertBodyIntoNoteContent "top" mode delegates to insertAtNoteBodyStart; insertTemplateIntoFile uses app.vault.process for atomic read-modify-write. Tests added for frontmatter-only/no-trailing-newline, correct blank-line separation, and CRLF preservation; test stub adds vault.process.
CaptureChoiceFormatter refactoring to use insertAtNoteBodyStart
src/formatters/captureChoiceFormatter.ts, src/formatters/captureChoiceFormatter-frontmatter.test.ts, src/formatters/captureChoiceFormatter-linebreak.test.ts
Replaces internal frontmatter-boundary helpers with insertAtNoteBodyStart for all top-insertion paths (default and CREATE_IF_NOT_FOUND_TOP variants); removes pos === -1 special-case in insertTextAfterPositionInBody. Adds frontmatter-aware top-insertion tests and updates a linebreak expectation.

Sequence Diagram

sequenceDiagram
  participant TemplateInsertEngine
  participant CaptureChoiceFormatter
  participant insertAtNoteBodyStart
  participant getFrontMatterInfo
  TemplateInsertEngine->>insertAtNoteBodyStart: insertBodyIntoNoteContent (top mode)
  CaptureChoiceFormatter->>insertAtNoteBodyStart: formatFileContent / createInsertAfterIfNotFound / createInsertBeforeIfNotFound / createInlineInsertAfterIfNotFound
  insertAtNoteBodyStart->>getFrontMatterInfo: getBodyStartOffset
  getFrontMatterInfo-->>insertAtNoteBodyStart: contentStart (body offset)
  insertAtNoteBodyStart-->>TemplateInsertEngine: safe insertion at body boundary
  insertAtNoteBodyStart-->>CaptureChoiceFormatter: safe insertion at body boundary
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • chhoumann/quickadd#1293: Overlaps on TemplateInsertEngine's existing-note insertion flow and frontmatter/body handling.
  • chhoumann/quickadd#1063: Related changes around CaptureChoiceFormatter frontmatter-aware insert-after/top logic.
  • chhoumann/quickadd#986: Prior work touching CaptureChoiceFormatter's "write to top" behavior that this PR refactors.

Suggested labels

released

Poem

🐰 Hopped the fence with nimble paws,
No glued lines, no awkward flaws,
CRLF kept tidy, newlines sing,
Shared helpers make the insertion spring,
A rabbit's cheer for cleaner code springs!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: frontmatter-aware top insertion for capture and apply-template' directly and clearly describes the main change: fixing frontmatter-aware insertion behavior for two features.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chhoumann/647-frontmatter-aware-append

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 13, 2026

Copy link
Copy Markdown

Deploying quickadd with  Cloudflare Pages  Cloudflare Pages

Latest commit: 55f95e2
Status: ✅  Deploy successful!
Preview URL: https://2327f741.quickadd.pages.dev
Branch Preview URL: https://chhoumann-647-frontmatter-aw.quickadd.pages.dev

View logs

The Obsidian docs recommend Vault.process over read+modify, and reading via
cachedRead before a write is the wrong primitive (cachedRead is documented as
display-only). Replace the cachedRead+modify pair in insertTemplateIntoFile with
a single atomic vault.process call wrapping insertBodyIntoNoteContent. Add a
process() impl to the test vaults (delegating to the modify spy).
@chhoumann chhoumann merged commit 0af26f5 into master Jun 13, 2026
10 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.

[FEATURE REQUEST] Appending to existing file while considering the frontmatter

1 participant