Skip to content

feat: option to insert template or capture links into the frontmatter of active note#1276

Open
ArjanSeijs wants to merge 3 commits into
chhoumann:masterfrom
ArjanSeijs:capture_to_frontmatter
Open

feat: option to insert template or capture links into the frontmatter of active note#1276
ArjanSeijs wants to merge 3 commits into
chhoumann:masterfrom
ArjanSeijs:capture_to_frontmatter

Conversation

@ArjanSeijs

@ArjanSeijs ArjanSeijs commented Jun 1, 2026

Copy link
Copy Markdown

Summary

This adds an option to insert links for templates and captures into the frontmatter of the active note. For my TTRPG vault i make heavy use of quickadd and needed this feature to quickly add links to the frontmatter of a file. For example for quickly adding npcs or sub locations to the frontmatter of my towns.

Changes

  • option to insert template or capture links into the frontmatter of active note

Testing / validation

Obsidian version 1.12.7
I updated the tests in linkPlacement.test.ts to account for the new LinkPlacement.
Testing the feature was done manually, see the gif for results.

Screenshot_2026-06-01_21-15-06 output

Checklist

  • PR title follows Conventional Commits
    — it becomes the squash-merge commit and drives the released version.
  • Linked any related issue(s).
  • Noted release/migration impact, if any.

Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Added "In frontmatter property" as a new link placement option for both capture and template choices.
    • UI now includes controls to specify the frontmatter property destination when using the new placement mode.
  • Documentation

    • Updated docs to describe the new frontmatter property link placement option and its usage.

@ArjanSeijs ArjanSeijs requested a review from chhoumann as a code owner June 1, 2026 20:07
@coderabbitai

coderabbitai Bot commented Jun 1, 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: 4aa29386-8684-4b6a-b4f5-3d2cbb95cb1b

📥 Commits

Reviewing files that changed from the base of the PR and between 02f1d0f and dde6b7a.

📒 Files selected for processing (4)
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/types/linkPlacement.ts
  • src/utilityObsidian.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/types/linkPlacement.ts
  • src/utilityObsidian.ts

📝 Walkthrough

Walkthrough

Adds an "inFrontmatter" link placement and optional frontmatterProperty option, updates types and tests, implements frontmatter-array append logic in insertion utilities, surfaces a configurable frontmatter property input in both choice builders, and updates docs to document the new mode.

Changes

Frontmatter Link Insertion

Layer / File(s) Summary
Type contract and validation
src/types/linkPlacement.ts, src/types/linkPlacement.test.ts
LinkPlacement adds newLine and inFrontmatter; exports placementSupportsFrontmatter; AppendLinkOptions gains frontmatterProperty?: string; normalizeAppendLinkOptions preserves it. Tests include inFrontmatter and predicate expectations.
Frontmatter insertion implementation
src/utilityObsidian.ts
insertLinkWithPlacement accepts frontmatterProperty; new mode === "inFrontmatter" branch validates the property and active file, initializes/validates an array frontmatter property via processFrontMatter, and appends the link text. insertFileLinkToActiveView forwards the property.
UI configuration in choice builders
src/gui/ChoiceBuilder/captureChoiceBuilder.ts, src/gui/ChoiceBuilder/templateChoiceBuilder.ts
Both builders import placementSupportsFrontmatter, add inFrontmatter to the placement dropdown, and conditionally render a "Frontmatter property" text input that maps to appendLink.frontmatterProperty while rebuilding appendLink (forcing linkType: 'link').
Documentation
docs/docs/Choices/CaptureChoice.md, docs/docs/Choices/TemplateChoice.md
Docs updated to include the new "In frontmatter property" append-link mode description.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • chhoumann/quickadd#984: Overlaps on append-link placement/type handling and ChoiceBuilder append-link propagation logic; #984 focuses on linkType/embed support while this PR adds inFrontmatter placement with frontmatterProperty.

Poem

🐰 I hop through code and fields of YAML bright,
I tuck a link where frontmatter sleeps at night,
The builders ask a name, the util makes the shove,
Tests nod, docs sing — a simple feature of love,
Hooray for small hops that make notes feel right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.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 pull request title clearly and accurately describes the main feature addition: inserting template or capture links into frontmatter. It is specific, concise, and directly reflects the substantial changes across documentation, UI builders, type definitions, and utility functions.
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 unit tests (beta)
  • Create PR with unit tests

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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 frontmatterProperty silently dropped when toggling link mode between required/optional

When a user toggles the "Enabled (requires active file)" / "Enabled (skip if no active file)" dropdown while the placement is set to "In frontmatter property", the onChange handler constructs a new appendLink object that includes placement, requireActiveFile, and linkType, but omits frontmatterProperty. The normalizedOptions captured at the top of addAppendLinkSetting() contains the frontmatterProperty value (via normalizeAppendLinkOptions at src/types/linkPlacement.ts:85), but it is not forwarded. After this.reload(), the frontmatter property text field resets to empty and the user's configured property name is lost.

The same issue exists in templateChoiceBuilder.ts:336-353.

(Refers to lines 791-806)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 frontmatterProperty silently dropped when toggling link mode in template choice builder

Same issue as in the capture choice builder: when a user toggles between "Enabled (requires active file)" and "Enabled (skip if no active file)" while placement is "In frontmatter property", the frontmatterProperty is not included in the new appendLink object (lines 337-342 and 345-350). The property name is silently lost after this.reload().

(Refers to lines 336-353)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/utilityObsidian.ts
Comment on lines +602 to +612
app.fileManager.processFrontMatter(file, (frontmatter) => {
if (frontmatter[frontmatterProperty] === undefined) {
frontmatter[frontmatterProperty] = []
}
if (!Array.isArray(frontmatter[frontmatterProperty])) {
const message = "Could not add into non array property:" + frontmatterProperty;
throw new Error(message)
}
frontmatter[frontmatterProperty].push(text)
})
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 processFrontMatter is async but not awaited, causing silent failures and race conditions

app.fileManager.processFrontMatter() returns a Promise<void> (confirmed by every other callsite in the codebase using await, e.g. src/engine/QuickAddEngine.ts:286), but the call at line 602 is not awaited. This causes three problems:

  1. The function returns (and insertFileLinkToActiveView at src/utilityObsidian.ts:768 returns true) before the frontmatter write has actually completed.
  2. The error thrown inside the callback for non-array properties (line 608) becomes an unhandled promise rejection instead of propagating to the caller.
  3. Subsequent operations after the insertFileLinkToActiveView call (e.g., opening the file in src/engine/TemplateChoiceEngine.ts:144) can race with the pending frontmatter write.

Since insertLinkWithPlacement is synchronous (function, not async function), this requires making it async or restructuring the frontmatter path to properly await the promise.

Prompt for agents
The insertLinkWithPlacement function at src/utilityObsidian.ts:552 is currently synchronous (returns void), but the new inFrontmatter code path calls app.fileManager.processFrontMatter which is async and returns Promise<void>. The promise is silently discarded.

To fix this properly, you need to either:
1. Make insertLinkWithPlacement async (returning Promise<void>) and await processFrontMatter. Then update insertFileLinkToActiveView (line 734) to also be async and await the call. Then update all callers (CaptureChoiceEngine.insertCaptureLink at line 173, TemplateChoiceEngine around line 141) to await as well.
2. Or, separate the frontmatter insertion into its own async function and handle it at a higher level in the engine code, keeping insertLinkWithPlacement synchronous for editor-based placements.

Option 1 is simpler but has a wider blast radius. Option 2 preserves the synchronous contract for existing placement modes while properly handling the async frontmatter path.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utilityObsidian.ts (1)

761-766: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update call site to handle async insertLinkWithPlacement.

If insertLinkWithPlacement is made async (as required to fix the missing await on processFrontMatter), this call site should also await the call and potentially propagate the async requirement up the call chain.

🔄 Proposed fix

Check if insertFileLinkToActiveView is already async or if it needs to become async:

-export function insertFileLinkToActiveView(
+export async function insertFileLinkToActiveView(
 	app: App,
 	file: TFile,
 	linkOptions: AppendLinkOptions,
 ): boolean {
 	// ... existing code ...
 
-	insertLinkWithPlacement(
+	await insertLinkWithPlacement(
 		app,
 		linkText,
 		linkOptions.placement,
 		{ requireActiveView: false, frontmatterProperty : linkOptions.frontmatterProperty },
 	);

Then check all call sites of insertFileLinkToActiveView to ensure they handle the async nature properly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utilityObsidian.ts` around lines 761 - 766, The call to
insertLinkWithPlacement must await the now-async function: update the call in
insertFileLinkToActiveView to use await insertLinkWithPlacement(...) and mark
insertFileLinkToActiveView as async if it isn’t already; then propagate async up
the call chain by updating any callers of insertFileLinkToActiveView to await it
(or handle the returned Promise). Ensure this ties together with the async
change to processFrontMatter so that frontmatter processing completes before
continuing.
♻️ Duplicate comments (1)
src/gui/ChoiceBuilder/templateChoiceBuilder.ts (1)

425-452: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing linkType field when updating appendLink.

This is the same issue as in captureChoiceBuilder.ts: when the user changes the frontmatter property (lines 444-449), the code reconstructs this.choice.appendLink but omits the linkType field, which will silently lose any previously selected embed link type.

🔧 Proposed fix
 		if (placementSupportsFrontmatter(normalizedOptions.placement)) {
-			const linkTypeSetting: Setting = new Setting(this.contentEl);
+			const frontmatterPropertySetting: Setting = new Setting(this.contentEl);
 			const current = typeof this.choice.appendLink !== "boolean" ? this.choice.appendLink.frontmatterProperty : '';
-			linkTypeSetting
+			frontmatterPropertySetting
 				.setName("Frontmatter property")
 				.setDesc("Choose the frontmatter property to insert the link into.")
 				.addText((text) => {
 					text.setValue(current ?? '')
 					text.onChange((value: string) => {
 						const currentValue = this.choice.appendLink;
 						const requireActiveFile =
 							typeof currentValue === "boolean"
 								? normalizedOptions.requireActiveFile
 								: currentValue.requireActiveFile;
 						const placement =
 							typeof currentValue === "boolean"
 								? normalizedOptions.placement
 								: currentValue.placement;
+						const linkType =
+							typeof currentValue === "boolean"
+								? normalizedLinkType
+								: currentValue.linkType ?? normalizedLinkType;

 						this.choice.appendLink = {
 							enabled: true,
 							placement,
 							requireActiveFile,
+							linkType,
 							frontmatterProperty: value,
 						};
 					});
 				});
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gui/ChoiceBuilder/templateChoiceBuilder.ts` around lines 425 - 452, When
updating this.choice.appendLink in the addText onChange handler (inside
placementSupportsFrontmatter block), preserve the existing linkType field so it
is not dropped; read linkType from the current this.choice.appendLink (or fall
back to normalizedOptions.linkType/default) and include it in the reconstructed
object along with enabled, placement, requireActiveFile, and frontmatterProperty
so linkType is retained (refer to placementSupportsFrontmatter,
this.choice.appendLink, normalizedOptions, and the addText onChange handler).
🧹 Nitpick comments (2)
src/types/linkPlacement.ts (1)

17-19: ⚡ Quick win

Add explicit return type annotation.

The function signature omits the return type. While TypeScript infers it correctly, exported functions should include explicit return types for clarity and to prevent unintended API changes.

♻️ Proposed fix
-export function placementSupportsFrontmatter(placement : LinkPlacement) : boolean {
+export function placementSupportsFrontmatter(placement: LinkPlacement): boolean {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/linkPlacement.ts` around lines 17 - 19, The exported function
placementSupportsFrontmatter currently lacks an explicit return type; update its
signature to include an explicit ": boolean" return annotation (e.g., export
function placementSupportsFrontmatter(placement: LinkPlacement): boolean) so the
function's return type is clear and stable; ensure the change is made on the
placementSupportsFrontmatter declaration and that it still returns a boolean
expression using the LinkPlacement type.
src/utilityObsidian.ts (1)

556-558: ⚡ Quick win

Consider using undefined instead of empty string as the default.

The current implementation defaults frontmatterProperty to an empty string, which then fails validation at line 593. Using undefined as the default (by omitting the default value assignment) would be more idiomatic TypeScript and clearer in intent.

♻️ Proposed fix
 export function insertLinkWithPlacement(
 	app: App,
 	text: string,
 	mode: LinkPlacement = "replaceSelection",
-	options: { requireActiveView?: boolean; frontmatterProperty?: string} = {},
+	options: { requireActiveView?: boolean; frontmatterProperty?: string } = {},
 ) {
-	const { requireActiveView = true, frontmatterProperty = '' } = options;
+	const { requireActiveView = true, frontmatterProperty } = options;

Then at line 593, check for undefined:

-	if(!frontmatterProperty) {
+	if (!frontmatterProperty) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utilityObsidian.ts` around lines 556 - 558, Change the default for
frontmatterProperty from an empty string to undefined by removing the
empty-string default in the options destructuring (update the const {
requireActiveView = true, frontmatterProperty } = options in the function that
declares options: { requireActiveView?: boolean; frontmatterProperty?: string
}). Then update the validation that currently fails when frontmatterProperty is
'' (the check around line 593) to explicitly test for undefined (e.g.,
frontmatterProperty === undefined or frontmatterProperty != null as appropriate)
so an omitted property is handled idiomatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/gui/ChoiceBuilder/captureChoiceBuilder.ts`:
- Around line 881-908: When rebuilding this.choice.appendLink in the frontmatter
property text.onChange handler, preserve the existing linkType instead of
omitting it: read linkType from currentValue when it's an object (or fall back
to normalizedOptions.linkType/default) and include linkType in the new object
along with enabled, placement, requireActiveFile, and frontmatterProperty so the
user's previously selected link type (e.g., embed) is not lost; modify the
text.onChange block that updates this.choice.appendLink to include linkType
sourced from currentValue || normalizedOptions.

In `@src/utilityObsidian.ts`:
- Around line 592-613: The processFrontMatter callback is awaited incorrectly:
call app.fileManager.processFrontMatter(...) must be awaited so the frontmatter
write completes before returning; update the function containing this block
(e.g., insertLinkWithPlacement) to be async, prefix the processFrontMatter call
with await, and update any callers (such as the call at the site that previously
invoked insertLinkWithPlacement) to await insertLinkWithPlacement as well;
ensure you still throw the same errors for invalid frontmatterProperty and
missing file and keep the existing logic inside the callback unchanged.

---

Outside diff comments:
In `@src/utilityObsidian.ts`:
- Around line 761-766: The call to insertLinkWithPlacement must await the
now-async function: update the call in insertFileLinkToActiveView to use await
insertLinkWithPlacement(...) and mark insertFileLinkToActiveView as async if it
isn’t already; then propagate async up the call chain by updating any callers of
insertFileLinkToActiveView to await it (or handle the returned Promise). Ensure
this ties together with the async change to processFrontMatter so that
frontmatter processing completes before continuing.

---

Duplicate comments:
In `@src/gui/ChoiceBuilder/templateChoiceBuilder.ts`:
- Around line 425-452: When updating this.choice.appendLink in the addText
onChange handler (inside placementSupportsFrontmatter block), preserve the
existing linkType field so it is not dropped; read linkType from the current
this.choice.appendLink (or fall back to normalizedOptions.linkType/default) and
include it in the reconstructed object along with enabled, placement,
requireActiveFile, and frontmatterProperty so linkType is retained (refer to
placementSupportsFrontmatter, this.choice.appendLink, normalizedOptions, and the
addText onChange handler).

---

Nitpick comments:
In `@src/types/linkPlacement.ts`:
- Around line 17-19: The exported function placementSupportsFrontmatter
currently lacks an explicit return type; update its signature to include an
explicit ": boolean" return annotation (e.g., export function
placementSupportsFrontmatter(placement: LinkPlacement): boolean) so the
function's return type is clear and stable; ensure the change is made on the
placementSupportsFrontmatter declaration and that it still returns a boolean
expression using the LinkPlacement type.

In `@src/utilityObsidian.ts`:
- Around line 556-558: Change the default for frontmatterProperty from an empty
string to undefined by removing the empty-string default in the options
destructuring (update the const { requireActiveView = true, frontmatterProperty
} = options in the function that declares options: { requireActiveView?:
boolean; frontmatterProperty?: string }). Then update the validation that
currently fails when frontmatterProperty is '' (the check around line 593) to
explicitly test for undefined (e.g., frontmatterProperty === undefined or
frontmatterProperty != null as appropriate) so an omitted property is handled
idiomatically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2852136a-95af-4bb7-9193-cdd0a1ff6149

📥 Commits

Reviewing files that changed from the base of the PR and between 1087fba and 02f1d0f.

📒 Files selected for processing (7)
  • docs/docs/Choices/CaptureChoice.md
  • docs/docs/Choices/TemplateChoice.md
  • src/gui/ChoiceBuilder/captureChoiceBuilder.ts
  • src/gui/ChoiceBuilder/templateChoiceBuilder.ts
  • src/types/linkPlacement.test.ts
  • src/types/linkPlacement.ts
  • src/utilityObsidian.ts

Comment on lines +881 to +908
if (placementSupportsFrontmatter(normalizedOptions.placement)) {
const linkTypeSetting: Setting = new Setting(this.contentEl);
const current = typeof this.choice.appendLink !== "boolean" ? this.choice.appendLink.frontmatterProperty : '';
linkTypeSetting
.setName("Frontmatter property")
.setDesc("Choose the frontmatter property to insert the link into.")
.addText((text) => {
text.setValue(current ?? '')
text.onChange((value: string) => {
const currentValue = this.choice.appendLink;
const requireActiveFile =
typeof currentValue === "boolean"
? normalizedOptions.requireActiveFile
: currentValue.requireActiveFile;
const placement =
typeof currentValue === "boolean"
? normalizedOptions.placement
: currentValue.placement;

this.choice.appendLink = {
enabled: true,
placement,
requireActiveFile,
frontmatterProperty: value,
};
});
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing linkType field when updating appendLink.

When the user changes the frontmatter property (lines 900-905), the code reconstructs this.choice.appendLink but omits the linkType field. This means that if the user had previously selected an embed link type, changing the frontmatter property will silently lose that setting and revert to undefined/default link type.

🔧 Proposed fix
 		if (placementSupportsFrontmatter(normalizedOptions.placement)) {
-			const linkTypeSetting: Setting = new Setting(this.contentEl);
+			const frontmatterPropertySetting: Setting = new Setting(this.contentEl);
 			const current = typeof this.choice.appendLink !== "boolean" ? this.choice.appendLink.frontmatterProperty : '';
-			linkTypeSetting
+			frontmatterPropertySetting
 				.setName("Frontmatter property")
 				.setDesc("Choose the frontmatter property to insert the link into.")
 				.addText((text) => {
 					text.setValue(current ?? '')
 					text.onChange((value: string) => {
 						const currentValue = this.choice.appendLink;
 						const requireActiveFile =
 							typeof currentValue === "boolean"
 								? normalizedOptions.requireActiveFile
 								: currentValue.requireActiveFile;
 						const placement =
 							typeof currentValue === "boolean"
 								? normalizedOptions.placement
 								: currentValue.placement;
+						const linkType =
+							typeof currentValue === "boolean"
+								? normalizedLinkType
+								: currentValue.linkType ?? normalizedLinkType;

 						this.choice.appendLink = {
 							enabled: true,
 							placement,
 							requireActiveFile,
+							linkType,
 							frontmatterProperty: value,
 						};
 					});
 				});
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gui/ChoiceBuilder/captureChoiceBuilder.ts` around lines 881 - 908, When
rebuilding this.choice.appendLink in the frontmatter property text.onChange
handler, preserve the existing linkType instead of omitting it: read linkType
from currentValue when it's an object (or fall back to
normalizedOptions.linkType/default) and include linkType in the new object along
with enabled, placement, requireActiveFile, and frontmatterProperty so the
user's previously selected link type (e.g., embed) is not lost; modify the
text.onChange block that updates this.choice.appendLink to include linkType
sourced from currentValue || normalizedOptions.

Comment thread src/utilityObsidian.ts
Comment on lines +592 to +613
if (mode === "inFrontmatter") {
if(!frontmatterProperty) {
const message = "Invalid frontmatter property: " + frontmatterProperty;
throw new Error(message);
}
const file = view.file;
if(!file) {
const message = "Could not find file of active view";
throw new Error(message);
}
app.fileManager.processFrontMatter(file, (frontmatter) => {
if (frontmatter[frontmatterProperty] === undefined) {
frontmatter[frontmatterProperty] = []
}
if (!Array.isArray(frontmatter[frontmatterProperty])) {
const message = "Could not add into non array property:" + frontmatterProperty;
throw new Error(message)
}
frontmatter[frontmatterProperty].push(text)
})
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing await on async processFrontMatter call.

The app.fileManager.processFrontMatter method returns a Promise<void>, but line 602 does not await it. This means the function returns before the frontmatter is actually written, which can cause race conditions or data loss if subsequent code assumes the frontmatter has been updated.

🐛 Proposed fix
-	if (mode === "inFrontmatter") {
-		if(!frontmatterProperty) {
+	if (mode === "inFrontmatter") {
+		if (!frontmatterProperty) {
 			const message = "Invalid frontmatter property: " + frontmatterProperty;
 			throw new Error(message);
 		}
 		const file = view.file;
-		if(!file) {
+		if (!file) {
 			const message = "Could not find file of active view";
 			throw new Error(message);
 		}
-		app.fileManager.processFrontMatter(file, (frontmatter) => {
+		await app.fileManager.processFrontMatter(file, (frontmatter) => {
 			if (frontmatter[frontmatterProperty] === undefined) {
-				frontmatter[frontmatterProperty] = []
+				frontmatter[frontmatterProperty] = [];
 			}
 			if (!Array.isArray(frontmatter[frontmatterProperty])) {
 				const message = "Could not add into non array property:" + frontmatterProperty;
-				throw new Error(message)
+				throw new Error(message);
 			}
-			frontmatter[frontmatterProperty].push(text)
-		})
-		return
+			frontmatter[frontmatterProperty].push(text);
+		});
+		return;
 	}

Note: This also requires making insertLinkWithPlacement an async function:

-export function insertLinkWithPlacement(
+export async function insertLinkWithPlacement(

And updating the call site at line 761 to await it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utilityObsidian.ts` around lines 592 - 613, The processFrontMatter
callback is awaited incorrectly: call app.fileManager.processFrontMatter(...)
must be awaited so the frontmatter write completes before returning; update the
function containing this block (e.g., insertLinkWithPlacement) to be async,
prefix the processFrontMatter call with await, and update any callers (such as
the call at the site that previously invoked insertLinkWithPlacement) to await
insertLinkWithPlacement as well; ensure you still throw the same errors for
invalid frontmatterProperty and missing file and keep the existing logic inside
the callback unchanged.

@ArjanSeijs

Copy link
Copy Markdown
Author

I can add that that the frontmatterProperty persists after changing the settings, however this not the case for the linkType since this is also just reset when switching between require vs skip active file.

For the processFrontMatter

  1. I can make it async but this would require to make a lot of functions async,
  2. or would you rather catch the error and create a new notice to inform the user.

@chhoumann

Copy link
Copy Markdown
Owner

Hey!

Thanks for working on this. I like the feature direction, and I can see why it would be useful for TTRPG/worldbuilding vaults.

I did some local testing in the dev vault, and I think this needs a bit more work before I can merge it.

On processFrontMatter: I’d prefer making the call chain async and awaiting it, rather than catching locally and showing a notice. The reason is that QuickAdd should know whether the link insertion actually succeeded. Right now the command can continue/report success while the frontmatter write fails later. In my dev-vault test, a scalar property caused processFrontMatter to throw, but the command still returned as executed and the error only showed up in dev tools.

One related thing to keep in mind: template/capture choices can also run inside macros. If frontmatter insertion errors are handled only locally with a notice, macro execution may still continue after a failed link insertion. I tested a small macro in the dev vault where step 1 hit the scalar-property frontmatter error and step 2 created a marker note; step 2 still ran. I don’t think this needs to be treated as a separate blocker by itself, but it’s a good reason to prefer awaiting/propagating the failure through the existing choice/macro error handling instead of making this a local-only notice.

The other bigger issue is YAML/frontmatter handling. At the moment this only works for missing/null properties and array properties. For example:

related:
  - "[[Existing]]"

works, but:

related: "[[Existing]]"

fails with Could not add into non array property:related.

That makes the feature feel more like “append to a frontmatter list property” than “insert into a frontmatter property”. I think we should either handle scalar values by converting them to a list, or make the UI/docs explicitly say that this only supports list properties. My preference would be a small helper with tests, roughly covering:

  • missing/null property -> create [newLink]
  • array property -> append newLink
  • string/scalar property -> convert to [existingValue, newLink]
  • object/map property -> reject with a clear error

I also found one settings issue: changing “Enabled (requires active file)” to “Enabled (skip if no active file)” drops frontmatterProperty from the saved appendLink object while keeping placement: "inFrontmatter". After that, running the choice fails with Invalid frontmatter property. So the settings handlers need to preserve frontmatterProperty when rebuilding appendLink.

Suggested next steps:

  1. Make the frontmatter insertion path async/awaited.
  2. Extract the YAML value handling into a small tested helper.
  3. Preserve frontmatterProperty across the append-link setting changes.
  4. Add regression tests for the array, scalar, missing/null, and settings-preservation cases.
  5. Clean up the small whitespace issue reported by git diff --check.

I’m happy to review another pass. The core idea is good; I just want this to be reliable across normal Obsidian frontmatter shapes before shipping it.

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