Skip to content

fix(template): preserve wikilink lists in frontmatter#1154

Open
chhoumann wants to merge 3 commits intomasterfrom
1140-bug-list-properties-with-links-dont-work
Open

fix(template): preserve wikilink lists in frontmatter#1154
chhoumann wants to merge 3 commits intomasterfrom
1140-bug-list-properties-with-links-dont-work

Conversation

@chhoumann
Copy link
Owner

@chhoumann chhoumann commented Mar 14, 2026

Preserve structured frontmatter arrays when template values contain multiple wikilinks.

QuickAdd was stringifying array values into the raw template output before Obsidian had a chance to post-process frontmatter. For values like [\"[[John Doe]]\", \"[[Jane Doe]]\"], that produced invalid YAML such as authors: [[John Doe]],[[Jane Doe]], so processFrontMatter() never got to rewrite the property as a list.

This change moves the structured-value checks into the shared YAML helpers and writes YAML-safe placeholders like [] during the initial replacement pass. Obsidian can then parse the file and apply the real array value through frontmatter post-processing.

I considered patching only the template engine, but the placeholder behavior belongs in the shared formatter/YAML path because Capture and other frontmatter flows use the same structured variable mechanism.

Added regression coverage in unit tests and a new obsidian-e2e spec that reproduces issue #1140 against the live dev vault.

Fixes #1140


Open with Devin

Summary by CodeRabbit

  • New Features

    • YAML-safe placeholders for structured template property values (arrays shown as [], objects, nulls, primitives) to keep frontmatter parseable.
    • Scoped template-property collection during formatting via a public collection scope to ensure structured values are gathered and applied after post-processing.
  • Tests

    • New unit tests for structured-value detection and placeholder generation.
    • New end-to-end tests validating template-property frontmatter (including wiki-link lists).

@chhoumann chhoumann linked an issue Mar 14, 2026 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Mar 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quickadd Ready Ready Preview Mar 16, 2026 7:54pm

@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

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: 9d61943c-4ce9-48cb-a93e-105eefa1b7af

📥 Commits

Reviewing files that changed from the base of the PR and between 54bb3d8 and c06f869.

⛔ Files ignored due to path filters (1)
  • bun.lockb is excluded by !**/bun.lockb
📒 Files selected for processing (2)
  • package.json
  • tests/e2e/template-property-links.test.ts

📝 Walkthrough

Walkthrough

Adds scoped template-property collection and YAML-placeholder handling: collectors can return structured YAML values during formatting, formatter writes YAML-safe placeholders (e.g., []) for structured values, and post-processing later replaces placeholders with the real structured values.

Changes

Cohort / File(s) Summary
YAML value helpers
src/utils/yamlValues.ts, src/utils/yamlValues.test.ts
Add isStructuredYamlValue() and getYamlPlaceholder() to detect structured YAML values and produce YAML-safe placeholders; tests added/extended.
Template property collector
src/utils/TemplatePropertyCollector.ts
Change maybeCollect() signature to return `unknown
Formatter core & tests
src/formatters/formatter.ts, src/formatters/formatter-template-property-types.test.ts
Add withTemplatePropertyCollection() depth scope; collect structured values during variable replacement and use getYamlPlaceholder() when available. Add testFormatWithTemplatePropertyCollection helper and update tests to expect YAML placeholders in initial writes.
Engine callers
src/engine/.../CaptureChoiceEngine.ts, src/engine/TemplateEngine.ts, src/engine/SingleTemplateEngine.ts
Wrap formatting calls with withTemplatePropertyCollection(() => ...) so template-property collection is enabled around formatting steps.
Test mocks
src/engine/.../*.test.ts (selection, notice, templateEngine-title, template-property-types)
Add passthrough withTemplatePropertyCollection methods to formatter mocks and adjust tests to assert placeholders are written while post-processing yields final structured values.
End-to-end tests
tests/e2e/template-property-links.test.ts
Add E2E test validating list properties with wikilinks (single & multi) to ensure placeholder + post-processing behavior works.
Package
package.json
Bump obsidian-e2e dependency from 0.4.0 to 0.6.0.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Template Engine
    participant Formatter as Formatter
    participant Collector as TemplatePropertyCollector
    participant YamlUtils as yamlValues
    participant PostProc as Post-processor

    Client->>Formatter: format frontmatter (within withTemplatePropertyCollection)
    Formatter->>Collector: maybeCollect(variable, context)
    Collector-->>Formatter: structuredValue? (value or undefined)
    alt structuredValue returned
        Formatter->>YamlUtils: getYamlPlaceholder(structuredValue)
        YamlUtils-->>Formatter: YAML-safe placeholder (e.g., "[]")
        Formatter-->>Client: write frontmatter with placeholder
    else no structuredValue
        Formatter-->>Client: write frontmatter with raw transformed string
    end
    Client->>PostProc: processFrontMatter(file)
    PostProc->>Collector: retrieve collected structured values
    Collector-->>PostProc: original structured values
    PostProc->>PostProc: apply structured values into YAML
    PostProc-->>Client: final file with typed YAML properties
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

released

Poem

🐰 I hopped through frontmatter, brackets bright,

Left tiny placeholders to keep YAML right,
Later the post-process fetched every array,
Replaced my marks and put links on display,
A rabbit's cheer — YAML sleeps tight tonight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.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 title 'fix(template): preserve wikilink lists in frontmatter' directly addresses the main issue: ensuring wikilink lists in frontmatter are preserved correctly as YAML-parseable structures.
Linked Issues check ✅ Passed The PR fully addresses issue #1140 by implementing YAML-safe placeholder writing, allowing Obsidian's YAML processor to parse frontmatter and apply structured values through post-processing.
Out of Scope Changes check ✅ Passed All changes align with the core objective of fixing wikilink list preservation: YAML helpers, formatter integration, test coverage, and dependency updates are all necessary for the fix.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1140-bug-list-properties-with-links-dont-work
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

@chhoumann chhoumann marked this pull request as ready for review March 14, 2026 19:21
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Mar 14, 2026

Deploying quickadd with  Cloudflare Pages  Cloudflare Pages

Latest commit: c06f869
Status: ✅  Deploy successful!
Preview URL: https://e3390cb4.quickadd.pages.dev
Branch Preview URL: https://1140-bug-list-properties-wit.quickadd.pages.dev

View logs

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00f404a984

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +401 to +403
const rawReplacement =
getYamlPlaceholder(structuredYamlValue) ??
this.getVariableValue(effectiveKey);

Choose a reason for hiding this comment

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

P2 Badge Avoid placeholder substitution outside frontmatter post-processing

This now replaces structured {{VALUE:...}} matches with placeholders ([], {}) as soon as maybeCollect returns a value, but Formatter.formatFileContent() is also used in flows that never call getAndClearTemplatePropertyVars()/processFrontMatter (for example the AI prompt formatting callback in MacroChoiceEngine.executeAIAssistant). In those contexts, YAML-frontmatter inputs lose their real structured variable content and keep placeholders in the final output, which is a functional regression from the previous raw-value replacement behavior when template property types are enabled.

Useful? React with 👍 / 👎.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/engine/CaptureChoiceEngine.ts (1)

631-635: Extract a helper for scoped formatting + property merge.

The same withTemplatePropertyCollection(...) + getAndClearTemplatePropertyVars() + mergeCapturePropertyVars(...) sequence appears four times. A helper would reduce drift risk and make future changes safer.

♻️ Refactor sketch
+	private async formatWithCollection(
+		work: () => Promise<string>,
+	): Promise<string> {
+		const formatted = await this.formatter.withTemplatePropertyCollection(work);
+		this.mergeCapturePropertyVars(
+			this.formatter.getAndClearTemplatePropertyVars(),
+		);
+		return formatted;
+	}
-		const formatted = await this.formatter.withTemplatePropertyCollection(
-			() => this.formatter.formatContentOnly(content),
-		);
-		this.mergeCapturePropertyVars(this.formatter.getAndClearTemplatePropertyVars());
+		const formatted = await this.formatWithCollection(() =>
+			this.formatter.formatContentOnly(content),
+		);
-		const formattedFileContent: string =
-			await this.formatter.withTemplatePropertyCollection(() =>
-				this.formatter.formatContentWithFile(
-					formatted,
-					this.choice,
-					fileContent,
-					file,
-				),
-			);
-		this.mergeCapturePropertyVars(this.formatter.getAndClearTemplatePropertyVars());
+		const formattedFileContent: string = await this.formatWithCollection(() =>
+			this.formatter.formatContentWithFile(
+				formatted,
+				this.choice,
+				fileContent,
+				file,
+			),
+		);

Also applies to: 640-649, 692-696, 752-762

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/CaptureChoiceEngine.ts` around lines 631 - 635, Extract a small
private helper on CaptureChoiceEngine (e.g., runScopedFormatting) that accepts a
callback or content string, invokes
this.formatter.withTemplatePropertyCollection(() =>
this.formatter.formatContentOnly(contentOrCallback)), then calls
this.mergeCapturePropertyVars(this.formatter.getAndClearTemplatePropertyVars())
and returns the formatted result; replace the four inline occurrences (the
blocks that call withTemplatePropertyCollection,
getAndClearTemplatePropertyVars, and mergeCapturePropertyVars) with calls to
this new helper to centralize the logic and avoid duplication.
src/formatters/formatter-template-property-types.test.ts (1)

27-27: Prefer explicit string coercion in the test override.

This keeps the override aligned with the string return contract and avoids leaking non-string runtime values through a cast.

♻️ Suggested tweak
 	protected getVariableValue(variableName: string): string {
-		return (this.variables.get(variableName) as string) ?? '';
+		const value = this.variables.get(variableName);
+		return value == null ? "" : String(value);
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/formatters/formatter-template-property-types.test.ts` at line 27, The
test override currently coerces a possibly non-string value via a TypeScript
cast "(this.variables.get(variableName) as string) ?? ''", which can leak
non-string runtime values; replace the cast with an explicit string coercion
using String(...) and preserve the fallback so the return value always matches
the string contract (e.g., use String(this.variables.get(variableName) ?? '')).
Update the expression inside the test helper where
"this.variables.get(variableName)" is used so the function/method returns a real
string rather than relying on "as string".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/engine/CaptureChoiceEngine.ts`:
- Around line 631-635: Extract a small private helper on CaptureChoiceEngine
(e.g., runScopedFormatting) that accepts a callback or content string, invokes
this.formatter.withTemplatePropertyCollection(() =>
this.formatter.formatContentOnly(contentOrCallback)), then calls
this.mergeCapturePropertyVars(this.formatter.getAndClearTemplatePropertyVars())
and returns the formatted result; replace the four inline occurrences (the
blocks that call withTemplatePropertyCollection,
getAndClearTemplatePropertyVars, and mergeCapturePropertyVars) with calls to
this new helper to centralize the logic and avoid duplication.

In `@src/formatters/formatter-template-property-types.test.ts`:
- Line 27: The test override currently coerces a possibly non-string value via a
TypeScript cast "(this.variables.get(variableName) as string) ?? ''", which can
leak non-string runtime values; replace the cast with an explicit string
coercion using String(...) and preserve the fallback so the return value always
matches the string contract (e.g., use String(this.variables.get(variableName)
?? '')). Update the expression inside the test helper where
"this.variables.get(variableName)" is used so the function/method returns a real
string rather than relying on "as string".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ac76770-f229-4658-9c3b-c52e43c55d25

📥 Commits

Reviewing files that changed from the base of the PR and between 00f404a and 54bb3d8.

📒 Files selected for processing (9)
  • src/engine/CaptureChoiceEngine.selection.test.ts
  • src/engine/CaptureChoiceEngine.ts
  • src/engine/SingleTemplateEngine.ts
  • src/engine/TemplateChoiceEngine.notice.test.ts
  • src/engine/TemplateEngine.ts
  • src/engine/templateEngine-title.test.ts
  • src/formatters/formatter-template-property-types.test.ts
  • src/formatters/formatter.ts
  • tests/e2e/template-property-links.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/template-property-links.test.ts

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.

[BUG] List properties with links don't work

1 participant