Skip to content

Fix template overwrite when filename casing differs#1049

Merged
chhoumann merged 2 commits intomasterfrom
fix/issue-599-template-case-overwrite
Dec 19, 2025
Merged

Fix template overwrite when filename casing differs#1049
chhoumann merged 2 commits intomasterfrom
fix/issue-599-template-case-overwrite

Conversation

@chhoumann
Copy link
Copy Markdown
Owner

@chhoumann chhoumann commented Dec 19, 2025

Summary

  • resolve template choice files by case-insensitive path when adapter.exists is true
  • add regression coverage for mismatched casing overwrite

Testing

  • bun run test

Fixes #599

Summary by CodeRabbit

  • Bug Fixes
    • Improved file resolution to handle existing files with different path casing
    • Enhanced error message when existing files cannot be resolved as valid markdown or canvas files

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 19, 2025

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

Project Deployment Review Updated (UTC)
quickadd Ready Ready Preview Dec 19, 2025 9:25pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 19, 2025

Warning

Rate limit exceeded

@chhoumann has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between aacb2a5 and 1f9d63a.

📒 Files selected for processing (1)
  • src/engine/TemplateChoiceEngine.ts (2 hunks)

Walkthrough

This PR fixes a bug where template choices could not overwrite existing files when the file path had different casing. It introduces case-insensitive file resolution logic that searches the vault when direct path lookup fails, and adds corresponding test coverage for this scenario.

Changes

Cohort / File(s) Summary
Test augmentation
src/engine/TemplateChoiceEngine.notice.test.ts
Added imports for TFile and fileExistsOverwriteFile constant; augmented test harness with getFiles mock and updated createEngine helper to expose mocked app instance; added new test suite verifying overwrite behavior for files with mismatched casing.
Core engine implementation
src/engine/TemplateChoiceEngine.ts
Introduced private helper method findExistingFile() that performs case-insensitive file resolution by first attempting direct path lookup, then searching vault files for case-insensitive matches; replaced direct getAbstractFileByPath() calls with the new helper; refined error message for unresolvable existing files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Case-insensitive matching logic: The findExistingFile() method's fallback search algorithm needs careful validation to ensure it correctly handles single and multiple matches without false positives
  • Error handling path: Verify the ambiguity detection and error logging when multiple case-insensitive matches exist
  • Test coverage validation: Ensure the new test adequately exercises the casing resolution flow and mocks are correctly configured

Poem

🐰 A file's case once caused quite the fright,
Now fuzzy matching makes overwrite right.
Whether "Bug" or "bug" meets the page,
This rabbit's fix ends the casing stage! ✨

Pre-merge checks and finishing touches

✅ 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 'Fix template overwrite when filename casing differs' directly and clearly describes the primary change in the PR, matching the core issue #599 about case-insensitive file overwriting.
Linked Issues check ✅ Passed The PR implements case-insensitive file resolution via a new findExistingFile helper and adds test coverage, fully addressing issue #599's requirement to handle filename casing differences during template file overwrites.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving issue #599: the new findExistingFile method, case-insensitive file matching logic, updated error messages, and test coverage for casing mismatches remain within the stated objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

Copy link
Copy Markdown

@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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/engine/TemplateChoiceEngine.ts (1)

206-226: Consider enhancing error details and adding documentation.

The case-insensitive resolution logic correctly addresses the bug. However, two optional improvements would help maintainability:

  1. Add JSDoc: Document the method's behavior, especially the case-insensitive fallback and multiple-match handling.
  2. Improve error message: When multiple matches are found (line 220-222), consider logging the matched paths to aid debugging.
🔎 Suggested enhancements
+	/**
+	 * Resolves an existing file by path, with case-insensitive fallback.
+	 * On case-insensitive filesystems, adapter.exists may return true even when
+	 * Obsidian's path index can't resolve the file due to casing differences.
+	 * 
+	 * @param filePath - The file path to resolve
+	 * @returns The resolved TFile, or null if not found or ambiguous
+	 */
 	private findExistingFile(filePath: string): TFile | null {
 		const direct = this.app.vault.getAbstractFileByPath(filePath);
 		if (direct instanceof TFile) return direct;
 		if (direct) return null;
 
 		// On case-insensitive filesystems, adapter.exists can return true even when
 		// Obsidian's case-sensitive path index can't resolve the file.
 		const lowerPath = filePath.toLowerCase();
 		const matches = this.app.vault
 			.getFiles()
 			.filter((file) => file.path.toLowerCase() === lowerPath);
 
 		if (matches.length === 1) return matches[0];
 		if (matches.length > 1) {
+			const matchedPaths = matches.map((f) => f.path).join(", ");
 			log.logError(
-				`Multiple files match '${filePath}' when ignoring case.`,
+				`Multiple files match '${filePath}' when ignoring case: ${matchedPaths}`,
 			);
 		}
 
 		return null;
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10c0402 and aacb2a5.

📒 Files selected for processing (2)
  • src/engine/TemplateChoiceEngine.notice.test.ts (4 hunks)
  • src/engine/TemplateChoiceEngine.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Source code lives in src/: core logic under engine/, services/, and utils/; Svelte UI in src/gui; shared types in src/types; settings entry in src/quickAddSettingsTab.ts

Files:

  • src/engine/TemplateChoiceEngine.ts
  • src/engine/TemplateChoiceEngine.notice.test.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.{ts,tsx}: Biome enforces tab indentation (width 2), LF endings, and an 80-character line guide; align editor settings
Use camelCase for variables and functions
Prefer type-only imports in TypeScript files
Route logging through the logger utilities for consistent output
Structure production code so Obsidian dependencies are injected behind interfaces; unit tests target pure logic and swap in adapters or tests/obsidian-stub.ts

Files:

  • src/engine/TemplateChoiceEngine.ts
  • src/engine/TemplateChoiceEngine.notice.test.ts
src/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (AGENTS.md)

Use PascalCase for classes and Svelte components

Files:

  • src/engine/TemplateChoiceEngine.ts
  • src/engine/TemplateChoiceEngine.notice.test.ts
🧠 Learnings (3)
📚 Learning: 2025-12-09T21:20:52.425Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T21:20:52.425Z
Learning: Applies to src/**/*.{ts,tsx} : Structure production code so Obsidian dependencies are injected behind interfaces; unit tests target pure logic and swap in adapters or `tests/obsidian-stub.ts`

Applied to files:

  • src/engine/TemplateChoiceEngine.notice.test.ts
📚 Learning: 2025-12-09T21:20:52.425Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T21:20:52.425Z
Learning: Applies to tests/**/*.{ts,tsx} : Add regression coverage for bug fixes

Applied to files:

  • src/engine/TemplateChoiceEngine.notice.test.ts
📚 Learning: 2025-12-09T21:20:52.425Z
Learnt from: CR
Repo: chhoumann/quickadd PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T21:20:52.425Z
Learning: Applies to src/**/*.{ts,tsx,js,jsx} : Source code lives in `src/`: core logic under `engine/`, `services/`, and `utils/`; Svelte UI in `src/gui`; shared types in `src/types`; settings entry in `src/quickAddSettingsTab.ts`

Applied to files:

  • src/engine/TemplateChoiceEngine.notice.test.ts
🧬 Code graph analysis (2)
src/engine/TemplateChoiceEngine.ts (2)
tests/obsidian-stub.ts (1)
  • TFile (332-338)
src/logger/logManager.ts (1)
  • log (59-59)
src/engine/TemplateChoiceEngine.notice.test.ts (3)
src/settingsStore.ts (1)
  • settingsStore (8-21)
tests/obsidian-stub.ts (1)
  • TFile (332-338)
src/constants.ts (1)
  • fileExistsOverwriteFile (168-168)
🔇 Additional comments (7)
src/engine/TemplateChoiceEngine.ts (2)

95-95: LGTM! Correct usage of the new helper.

The switch from direct getAbstractFileByPath to findExistingFile correctly addresses the case-insensitive filesystem issue.


101-101: LGTM! Clearer error message.

The updated message better explains why the operation failed (file exists but couldn't be resolved as the expected type).

src/engine/TemplateChoiceEngine.notice.test.ts (5)

101-101: LGTM! Import needed for test instantiation.

The TFile class import (not just the type) is required to construct test instances at line 293.


108-108: LGTM! Import needed for test scenario.

The fileExistsOverwriteFile constant is used at line 299 to configure the test case.


161-161: LGTM! Mock supports case-insensitive fallback.

The getFiles mock is needed for the new case-insensitive search logic in findExistingFile.


198-198: LGTM! Exposing app enables test configuration.

Returning app from the helper allows tests to configure vault mocks for specific scenarios.


279-332: Excellent regression test coverage!

This test correctly exercises the exact bug scenario from issue #599:

  • File exists on filesystem with one casing ("Bug report.md")
  • Template choice uses different casing ("Bug Report")
  • Direct path lookup fails (simulated by getAbstractFileByPath returning null)
  • Case-insensitive fallback finds the existing file
  • Overwrite operation proceeds successfully

The test setup properly mocks all three conditions needed to reproduce the bug. This aligns with the learning that regression coverage should be added for bug fixes.

Based on learnings, regression coverage for bug fixes is expected.

@chhoumann chhoumann merged commit 0140556 into master Dec 19, 2025
4 checks passed
@chhoumann chhoumann deleted the fix/issue-599-template-case-overwrite branch December 19, 2025 21:27
github-actions Bot pushed a commit that referenced this pull request Jan 28, 2026
# [2.10.0](2.9.4...2.10.0) (2026-01-28)

### Bug Fixes

* add insert-after blank-line mode ([#1056](#1056)) ([231e908](231e908))
* backfill file opening defaults for legacy choices ([bfffd46](bfffd46))
* default update announcements to major ([#1042](#1042)) ([4e5659b](4e5659b))
* handle template overwrite with case-mismatched paths ([#1049](#1049)) ([0140556](0140556))
* harden suggester display items ([5460657](5460657))
* harden suggester filtering on Android ([4560f22](4560f22)), closes [#1078](#1078)
* normalize leading slashes in capture/template paths ([#1050](#1050)) ([1c7def1](1c7def1))
* normalize unicode in file suggestions ([#1046](#1046)) ([10c0402](10c0402))
* pin obsidian types to 1.11.4 for SecretStorage API ([ddbf6f6](ddbf6f6))
* prefill macro rename input ([#1043](#1043)) ([06c4a25](06c4a25))
* preserve blank lines for insert after ([#1054](#1054)) ([818c036](818c036))
* preserve variables for VALUE templating ([36d43ba](36d43ba))
* reduce bundle size below sync limit ([1e1a632](1e1a632))
* restore compatibility with Templater 2.18.0 ([716f2d9](716f2d9)), closes [#1085](#1085) [#1086](#1086)

### Features

* add back class to choice suggester ([#1047](#1047)) ([4c8cfe9](4c8cfe9))
* add capture selection-as-value controls ([#1055](#1055)) ([250768a](250768a))
* add inline insert-after capture mode ([b2e1ef5](b2e1ef5))
* add macro selection helper ([786b53c](786b53c)), closes [#483](#483)
* add native date picker prompt ([2811c5a](2811c5a))
* add per-token multiline VALUE inputs ([98fa7db](98fa7db)), closes [#339](#339)
* add versioned documentation with Docusaurus ([03c2d3e](03c2d3e))
* adopt obsidian 1.11 settings APIs ([#1041](#1041)) ([15c4b34](15c4b34))
* default multi placeholder to name ([fcd058f](fcd058f))
* enhance template folder chooser ([f1e2a9f](f1e2a9f)), closes [#1011](#1011) [#1012](#1012)
* improve prompt labeling for VALUE/MACRO and multi choices ([78fd184](78fd184))
* persist input prompt drafts on cancel/escape ([#1044](#1044)) ([62a67f4](62a67f4))
* store AI provider keys in SecretStorage ([4559013](4559013))
* support short-form date aliases ([e04a3f6](e04a3f6))
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 2.10.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Can't overwrite existing file if the case is mismatched

1 participant